JPA

SpringBoot JPA 게시판 CRUD 구현(Update-게시글 수정)

빈코 2022. 7. 21. 20:16

지난번에 구현한 페이징 처리까지 R(Read)에 해당됩니다. 오늘은 U(Update)에 해당되는 게시글 수정 WAS 단 로직을 포스팅하려 합니다. 약간 패키지성 포스팅이어서 게시판의 Create, Read 포스팅을 보지 않으셨다면 헷갈리실 수 있습니다. 맨 아래에 링크를 통해서 지난번 포스팅을 보시고 오시거나 깃허브 Bincolog를 참고하시는 것을 추천드립니다!

 

로고
게시글 수정

 

Request 생성📗

첫번째로는 Request를 생성합니다. Create, Read와 동일하게 Entity를 사용하지 않고 따로 클래스를 만들어줍니다. 이유는 지난 포스팅에서 언급했습니다 :) 

 

@Getter
@Builder
public class PostEdit {
    @NotBlank(message = "제목을 입력해주세요.")
    private String title;
    @NotBlank(message = "내용을 입력해주세요.")
    private String content;
    @Builder
    public PostEdit(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

게시글 작성 때 만든 DTO인 PostCreate와 코드는 같습니다. 하지만 왜 코드가 같은데도 클래스를 나눌까요? 추후에 만약 게시글을 생성할 때 유효성 검사와 같은 로직이 추가된다면, 같은 클래스로 생성을 하고 수정을 하는 상황에서 문제가 발생할 수 있기 때문입니다.

 

@Getter, @Builder, @NotBlank 어노테이션은 게시글 작성 포스팅을 참고해주세요😃

 

에디터 생성📘

Request를 생성했다면 이후엔 에디터를 생성해줍니다. 에디터는 왜 만들까요? 수정 로직에서 에디터 클래스를 따로 분류하면 수정할 필드만 선언할 수 있는 이점이 있습니다.

 

또한, 하단에 Service 코드를 구현할 때 이해가 쉽게 되겠지만 앞서 설명하자면 만약에 게시글을 수정하는 상황이라고 가정해보겠습니다. 수정을 하기 위해선 기존 게시글을 조회해서 가져오고 이후에 (에디터가 없다면) Request인 PostEdit에 바로 Build를 할 것입니다. 그러고 나서 또 PostEdit에서 변경된 값을 수정하고 Post로 빌드 후 저장하는 과정이 추가가 됩니다.

 

즉, 필요 없는 코드가 추가될 우려가 있습니다. 그렇기에 에디터를 만들어서 진행한다면, 기존 게시글을 가져오고 Editor에 값을 담습니다. 이전과 다를 게 없어 보이지만 에디터는 데이터를 픽스되지 않게 구현할 것입니다. 픽스되지 않기 때문에 바로 Post로 치환하여 수정 로직을 보다 간단하게 구현할 수 있습니다.

 

이해가 안되신다면 하단 Service 로직을 보고 이해하시면 될 것 같습니다 :)

@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;
    }
}

 

Controller 생성📙

Controller는 게시글 작성, 게시글 조회와 똑같이 간단하게 구현이 됩니다.

@PatchMapping("/posts/{postId}")
public void edit(@PathVariable Long postId, @RequestBody @Valid PostEdit request){
    postService.edit(postId, request);
}

 

Spring 4.3버전 이후로 @PatchMapping을 사용할 수 있게 되었습니다. 기존에는 @PutMapping으로 정보를 수정하는 로직을 구성하였습니다. 대게 클라이언트가 기존 리소스를 완전히 교체해야 하는 경우는 Put을 사용하고, 부분 업데이트를 수행할 때는 Patch를 사용합니다. Put과 Patch의 차이점을 좀 더 알고 싶으신 분은 아래 링크를 통해 확인해주세요!

 

 

 

HTTP PUT vs HTTP PATCH in a REST API | Baeldung

Learn about the difference between PUT and PATCH HTTP methods, and their implications when building your Spring API.

www.baeldung.com

Controller의 edit() 메서드는 @PathVariable로 넘겨받은 게시글의 아이디 postId와 @RequestBody로 넘겨받은 PostEdit 클래스인 request를 파라미터로 받습니다.

 

여기서 @Valid는 PostEdit 클래스를 생성할 때 @NotBlank를 수행해주는 것 잊지 않으셨죠?

 

마지막으로는 postService의 edit() 메서드를 파라미터 값 postId와 수정할 내용이 담긴 request를 넘겨줍니다.

 

Service 생성📔

Service 클래스에서는 넘겨받은 게시글 id값을 이용하여 게시글을 찾고 수정 로직을 구현하면 됩니다.

 

    @Transactional
    public void edit(Long id, PostEdit postEdit) {
        Post post = postRepository.findById(id)
                .orElseThrow(RuntimeException::new);

        PostEditor.PostEditorBuilder postEditorBuilder = post.toEditor();

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

        post.edit(postEditor);
    }

 

📌Transactional이 무엇일까요? 간단하게 설명하면 일종의 '거래'라고 생각하시면 될 것 같습니다. 만약에 A라는 사람이 B에게 입금을 하였는데, 시스템 오류로 B라는 사람이 입금받은 게 없다면 어떻게 될까요? 당연히 거래 취소가 되어야 합니다. 프로그램도 마찬가지로 @Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소해줍니다.

 

첫 번째 로직은 JpaRepository에서 기본적으로 제공해주는 findById() 메서드를 이용하여 해당 게시글을 조회합니다. 만약 게시글이 없다면 예외처리를 발생시키는데, RuntimeException은 추후에 커스터마이징 한 예외처리로 바꿀 예정입니다.

 

그리고 찾아온 게시글 즉 Entity를 바로 수정하면 안 되겠죠? 위에서 만든 PostEditor를 이용하여 Entity를 editor로 치환해줍니다. 이후에 Build 패턴을 이용하여 컨트롤러부터 넘겨받은 PostEdit의 값을 넣어줍니다.

 

Post
Post 클래스 toEditor

 

Post
Post 클래스 edit

혹여나 따라 하시는 분들은 이전에 Post 클래스에 추가하지 않았던 메서드 2개입니다. 추가하셔야 오류가 발생하지 않으니 꼭 추가해주세요😃

 

다시 Service 클래스의 마지막 부분인 post.edit() 메서드를 이용하여 Entity에 변경된 값을 주입시켜줍니다. 그리고 Service 로직은 끝이 납니다. 여기서 궁금한 점이 있지 않으신가요? 기존에는 저장했을 때, save() 메서드를 이용하였는데, 수정 메서드는 save 메서드가 없이 로직이 끝이 납니다.

 

서비스에 트랜잭션이 달린 경우 메서드가 시작될 때 save 메서드가 함께 실행됩니다. 다만 오류가 발생하였을 때, 모두 무효화시킬 뿐입니다. 그렇기 때문에 트랜잭션이 달린 메서드는 따로 저장하는 코드를 구현하지 않습니다.


Controller Test 생성📒

이제 비즈니스 로직을 다 구현했으니 Test 케이스를 글 제목 수정 로직으로 작성해보겠습니다.

    @Test
    @DisplayName("글 제목 수정")
    void test7() throws Exception{
        //given
        Post post = Post.builder()
                .title("빈코 제목")
                .content("빈코 내용")
                .build();

        postRepository.save(post);

        PostEdit postEdit = PostEdit.builder()
                .title("빈코 제목 수정")
                .content("빈코 내용")
                .build();

        // expected
        mockMvc.perform(MockMvcRequestBuilders.patch("/posts/{postId}", post.getId())
                        .contentType(APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(postEdit))
                )
                .andExpect(status().isOk())
                .andDo(print());
    }

 

given section

given 단계에서는 DB에 들어갈 기존 게시글을 하나 생성해 줍니다. 이전과 마찬가지로 Builder 패턴을 이용하여 게시글을 만들고 save() 메서드를 이용하여 저장하였습니다. 

 

그리고 수정할 PostEdit 객체도 만들어 줍니다. 이 부분도 Builder 패턴을 이용하여 구현하였습니다.

expected section

expected 단계에서는 mockMvc을 이용하여 url을 매핑해주고 Controller에 DB에 저장한 기존 게시글의 아이디와 수정할 내용을 담은 postEdit을 넘겨줍니다.

 

objectMapper.writeValueAsString()은 직관적으로 알 수 있지만, 수정할 내역을 담은 객체인 postEdit을 String 타입으로 변환해 주는 역할을 합니다.

 

그리고 기댓값으로 200 코드가 내려오는지 확인하였습니다. Controller를 거쳐 Service 로직을 탔을 때, 예외처리 로직을 구현했기에 테스트 케이스는 많이 작성하지 않았습니다. 예외가 발생하면 200코드가 아닌 다른 코드가 내려오기 때문입니다. 하지만 더 궁금하신 분들은 독자분들만의 테스트를 추가해보셔도 좋을 것 같네요😄

 

테스트
성공!

 

Sercie Test 생성📚

Service 테스트는 Controller 테스트와 많이 유사하게 작성됩니다.

    @Test
    @DisplayName("글 제목 수정")
    void test5() {
        //given
        Post post = Post.builder()
                .title("빈코 제목")
                .content("빈코 내용")
                .build();

        postRepository.save(post);

        PostEdit postEdit = PostEdit.builder()
                .title("빈코 제목수정")
                .content("빈코 내용")
                .build();

        //when
        postService.edit(post.getId(),postEdit);

        //then
        Post changePost = postRepository.findById(post.getId())
                .orElseThrow(() -> new RuntimeException("글이 존재하지 않습니다. id=" + post.getId()));

        Assertions.assertEquals("빈코 제목수정", changePost.getTitle());
        Assertions.assertEquals("빈코 내용", changePost.getContent());
    }

 

given section

given 단계에서는 Controller 테스트와 마찬가지로 기존 게시글을 하나 저장해주고, 수정할 postEdit 객체를 하나 생성해줍니다.

when section

when 단계에서는 postService.edit() 메서드에 파라미터 값으로 기존에 저장한 게시글의 Id와 변경할 객체인 postEdit을 넘겨주게 됩니다. 그리고 아까 언급드린 PostService에 Edit() 메서드는 트랜잭션으로 구현되었기 때문에, 자동으로 변경된 값을 저장해 줄 것입니다.

then section

then 단계에서는 findById() 메서드를 이용하여 게시글을 조회하고, 만약 글이 존재하지 않을 시 예외처리를 추가하였습니다. 글이 존재한다면 assertEquals 메서드를 이용하여 게시글 제목이 변경되었는지, 기존 내용은 동일한지 체크해주었습니다.

 

테스트
성공!

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며

지금까지 게시글 수정 로직에 대해 포스팅을 하였습니다. 예외처리 부분을 먼저 진행해야 할지, 삭제 기능까지 구현하고 예외처리는 모아서 한 번에 할지는 아직까지도 고민이네요. 코드를 구성할 때 클래스를 왔다 갔다 하면서 작성하지만 블로그에서는 그렇게 설명하기가 힘들어서 어떻게 하면 이해가 잘 될지(몰론 미래의 저를 위해서도^^..)  고민이 많이 됩니다.

 

부족한 부분이나 궁금하신 점은 댓글로 남겨주세요😀

 

 

이전 포스팅

* [ SpringBoot JPA 게시판 CRUD(Create) ]

* [ SpringBoot JPA 게시판 CRUD(Create-TDD) ]

* [ SpringBoot JPA 게시판 CRUD(Read 단건 조회 및 TDD) ]

* [ SpringBoot JPA 게시판 CRUD(Read 다중 조회 및 페이징 처리 / TDD) ]

 

다음 포스팅

* [ SpringBoot JPA 게시판 CRUD(Delete-TDD) ]

* [ SpringBoot JPA 게시판 CRUD(예외처리) ]

반응형