Programming/Spring

Spring - 게시글 수정 / 삭제

잇(IT) 2023. 9. 5. 21:27

- 게시글을 수정 및 삭제하는 방법에 대해 알아 볼 것이다.


- 게시글 수정

- PostEdit.java

@Getter
@Setter
@ToString
public class PostEdit {

    @NotBlank(message = "title을 입력해주세요")
    private String title;

    @NotBlank(message = "content를 입력해주세요")
    private String content;

    @Builder
    public PostEdit(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

- PostService.java

@Transactional
public void edit(Long id, PostEdit postEdit) {
     Post post = postRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글 입니다."));
            
//        post.change(postEdit.getTitle(), postEdit.getContent());

PostEditor.PostEditorBuilder editorBuilder = post.toEditor();

        PostEditor postEditor = editorBuilder.title(postEdit.getTitle())
                .content(postEdit.getContent())
                .build();

        post.edit(postEditor);

 

- @Transactional 어노테이션을 사용하면 해당 메서드의 데이터의 일관성을 유지하기 위해 save() 메서드를 별도로 작성하지 않아도 스프링에서 DB에 저장을 한다. 만약 문제가 생기면 rollback을 한다.

- DB에서 id값을 통해 엔티티를 찾아올 때 null이 반환 될 수 있기 때문에 Optional로 반환하는 것이 맞지만, 해당 엔티티(Post)로 반환하기 위해 엔티티가 아닌 다른 값이 들어오게 될 경우 예외로 처리하여 던져버린다.

 

1. 엔티티 수정

- 엔티티의 값을 수정하는 가장 기본적인 방법은 변경할 값을 받을 클래스(postEdit)를 생성하여 해당 값을 받아 기존의 값(Post)에 변경하는 메서드(change())를 생성하여 넣는 방법이 있다.

- 위 방법의 경우 가장 기본적인 방법이지만 위와 같이 파라미터의 수가 적을 경우 괜찮지만 파라미터가 엄청나게 늘어나게 되면, 파라미터 순서에 의해서 원하는 값이 원하는 필드에 들어가지 않는 일이 발생할 수 있기 때문에 대비를 해야한다.

 

- Post.java

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @Lob // Java에서는 String이지만 DB에 저장될 때는 Long text로 저장되게 하기 위한 어노테이션이다.
    private String content;

    @Builder
    public Post(String title, String content) {
        this.title = title;
        this.content = content;
    }

//    public void change(String title, String content) {
//        this.title = title;
//        this.content = content;
//    }
    //위의 경우 파라미터 순서가 변경되면 버그를 찾기 매우 힘들다. 이를 대비해 아래와 같이 사용한다.

    public PostEditor.PostEditorBuilder toEditor() {
        return PostEditor.builder()
                .title(title)
                .content(content);
    }

    // Builder를 넘기는 이유는 build()를 쓰게되면 객체를 생성하며 데이터를 확정시키기 때문이다.
    // 데이터를 확정 시키지 않고 넘기기 위해 Builder 형태로 전달한다.

    public void edit(PostEditor postEditor) {
        title = postEditor.getTitle();
        content = postEditor.getContent();
    }
}

 

- PostEditor.java

@Getter
public class PostEditor {

    private final String title;
    private final String content;

    @Builder
    public PostEditor(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public static PostEditorBuilder builder() {
        return new PostEditorBuilder();
    }

    public static class PostEditorBuilder {
        private String title;
        private String content;

        PostEditorBuilder() {
        }

        public PostEditorBuilder title(final String title) {
            if (title != null) {
                this.title = title;
            }
            return this;
        }

        public PostEditorBuilder content(final String content) {
            if (content != null) {
                this.content = content;
            }
            return this;
        }

        public PostEditor build() {
            return new PostEditor(this.title, this.content);
        }

        public String toString() {
            return "PostEditor.PostEditorBuilder(title=" + this.title + ", content=" + this.content + ")";
        }
    }
}

2. 수정 엔티티 생성

- 수정용 엔티티를 생성하여 수정용 엔티티에 값을 넣고 해당 값을 실제 엔티티에 넣어 수정하는 방식이다.

 

- Post.java의 코드에서 아래의 toEditor()메서드를 호출하게 되면 PostEditor 엔티티의 Builder 패턴을 반환한다.

public PostEditor.PostEditorBuilder toEditor() {
        return PostEditor.builder()
                .title(title)
                .content(content);
    }

- build()를 하게 되면 builder 패턴을 통해 객체를 생성하지만 build()메서드를 호출하지 않고, [엔티티명]Builer를 통해 데이터를 확정짓지 않고 builder 패턴을 반환할 수 있다.

- 추가로 @Builder 어노테이션이 붙은 생성자의 경우 build() 메서드가 호출되는 시점에 해당 생성자 파라미터에 builder클래스에 담겨있던 값이 넘어간다.

- PostSerivce.java의 코드에선 아래와 같이 toEditor()메서드를 호출하여, Controller에서 넘어온 postEdit 객체에 담긴 데이터를 builder 패턴을 통해 PostEditor 엔티티에 저장한다. 즉, 수정하기 위한 값을 받아 수정 엔티티에 담는다.

 

PostEditor.PostEditorBuilder editorBuilder = post.toEditor();

        PostEditor postEditor = editorBuilder.title(postEdit.getTitle())
                .content(postEdit.getContent())
                .build();

- Post.java 코드에 edit(PostEditor postEditor)메서드를 받아옴으로서 이전에 여러가지 파라미터를 받아오는 대신 PostEditor 객체 하나만 파라미터로 받아올 수 있기 때문에 가독성 측면에서 좋고, 또 다른 개발자가 볼 때 어떤 값이 수정 가능한 값이지 쉽게 확인할 수 있는 장점이 있다.

public void edit(PostEditor postEditor) {
        title = postEditor.getTitle();
        content = postEditor.getContent();
    }
}

- PostService.java 코드에 edit() 메서드를 추가함으로서 수정 controller가 실행되었을 때 수정 엔티티에 해당하는 PostEditor에 개발자가 의도한 필드의 값들이 주입되고 @Transactional 어노테이션에 의해 변경된 Post 엔티티가 DB에 저장(수정) 된다.

post.edit(postEditor);

- controller에서 service에 수정을 요청하게 되면 그에 해당하는 Service의 edit()메서드가 호출되고 그 안에 post.edit()이 실행되어 수정 코드가 실행된다.

 

- PostEditor.java

.....

public static PostEditorBuilder builder() {
        return new PostEditorBuilder();
    }

    public static class PostEditorBuilder {
        private String title;
        private String content;

        PostEditorBuilder() {
        }

        public PostEditorBuilder title(final String title) {
            if (title != null) {
                this.title = title;
            }
            return this;
        }

        public PostEditorBuilder content(final String content) {
            if (content != null) {
                this.content = content;
            }
            return this;
        }

        public PostEditor build() {
            return new PostEditor(this.title, this.content);
        }

        public String toString() {
            return "PostEditor.PostEditorBuilder(title=" + this.title + ", content=" + this.content + ")";
        }
    }

- PostEditor코드를 보게되면 builder 클래스의 코드를 사용하는 것을 확인 할 수 있다.

- 기존의 builder 클래스의 코드를 그대로 가져와서

.....

public PostEditorBuilder title(final String title) {
            if (title != null) {
                this.title = title;
            }
            return this;
        }

        public PostEditorBuilder content(final String content) {
            if (content != null) {
                this.content = content;
            }
            return this;
        }
        
.....

- 위 코드에서 if문을 추가함으로서 build 클래스를 사용할 때 기존에 값이 있었다면 해당 값을 그대로 사용하기 위해 코드를 변경하였다.

 

- controller에서 수정 Service가 호출되면 아래의 post 클래스의 toEditor() 메서드 및 builder 패턴을 통해 postEdit의 값들을 넣어 새로운 PostEditor 객체를 생성한다.

.....

PostEditor.PostEditorBuilder editorBuilder = post.toEditor();

        PostEditor postEditor = editorBuilder.title(postEdit.getTitle())
                .content(postEdit.getContent())
                .build();
                
.....

 

- 최종적으로 Service의 post.edti() 메서드가 호출되고

.....

post.edit(postEditor);


.....

- 아래와 같이 edit() 메서드를 통해 넘어온 postEditor 객체의 값들이 post 객체에 주입된다.

.....

public void edit(PostEditor postEditor) {
        title = postEditor.getTitle();
        content = postEditor.getContent();
    }
    
.....

- 마지막으로 값이 변경된 Post 엔티티는 @Transactional 어노테이션에 의해 자동으로 변경된 부분이 DB에 저장되게 된다.


- 게시글 삭제

- 게시글 삭제의 경우 JPA 기본적으로 생성되어 있는 delete() 메서드를 통해 삭제 메서드를 실행한다.

 

- PostServiceTest.java

@Test
    @DisplayName("게시글 삭제")
    void test6() {
        // given
        Post post = Post.builder()
                .title("BIS")
                .content("상도더샵")
                .build();

        postRepository.save(post);

        // when
        postService.delete(post.getId());

        // then
        Assertions.assertEquals(0, postRepository.count());
    }
}

 

- PostService.java

.....

public void delete(Long id) {
        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글 입니다."));
        // -> 존재하는 경우
        postRepository.delete(post);
    }
}
728x90