개발/Spring(Hodol)

Spring - 게시글 조회, 클래스 분리 (단건)

잇(IT) 2023. 8. 29. 16:26
728x90

 

- 게시글 단건 조회

 

- PostService.java

...

    public Post get(Long postId) {
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다."));
        return post;
    }
}

- Jpa를 통해 postRepository에서 findById를 통해 Id값에 해당하는 Post 엔티티 값을 가져온다.

- 기본적으로 값이 없을 경우 null이 반환되기 때문에 Optional로 반환 타입을 지정해야 하지만 참조형을 Post 엔티티로 지정하는 대신 null과 같이 예외가 발생하는 경우 던져버리는 형식으로 바꿔서 사용할 수 있다.

 

* Optional을 사용하는 경우

 public Post get(Long postId) {
        Optional<Post> post = postRepository.findById(postId);

 

- PostController.java

.......

/*
     * /posts -> 글 전체 조회(검색 + 페이징)
     * /posts/{postId} -> 글 한개만 조회
     * */
    @GetMapping("/posts/{postId}")
    public Post get(@PathVariable Long postId) {
        Post post = postService.get(postId);
        return post;
    }
}

- Get방식으로 id값이 넘어오면 Service의 get() 메서드를 통해 Post 엔티티 즉, 글을 조회하는 메서드이다.


- Test 코드

- PostServiceTest.java

@SpringBootTest
class PostServiceTest {

    @Autowired
    private PostService postService;

    @Autowired
    private PostRepository postRepository;

    @Test
    @DisplayName("글 작성")
    void test1() {
        //given
        PostCreate postCreate = PostCreate.builder()
                .title("제목입니다.")
                .content("내용입니다.")
                .build();

        //when
        postService.write(postCreate);

        //then
        Assertions.assertEquals(1L, postRepository.count());
        Post post = postRepository.findAll().get(0);
        assertEquals("제목입니다.", post.getTitle());
        assertEquals("내용입니다.", post.getContent());
    }
    
    @Test
    @DisplayName("글 1개 조회")
    void test2() {
        //given
        Post request = Post.builder()
                .title("name")
                .content("insoo")
                .build();

        postRepository.save(request);
        //when
        postService.get(request.getId());
        //then
        assertEquals(1L, postRepository.count());
        assertEquals("name", request.getTitle());
        assertEquals("insoo",request.getContent());
    }
}

- Service의 write()는 파라미터로 DTO를 받아서 해당 DTO의 값을 엔티티에 넣어 DB에 저장한다.

- 그 후 메모리에 임시로 생성된 H2 데이터베이스에서 모든 엔티티를 조회한 다음(위의 상황에선 한개이기 때문에 결국 첫번째 엔티티가 저장한 엔티티와 일치한다.) 해당 엔티티의 title과 content를 비교해본다.

 

- get()메서드의 경우 Long타입의 id값을 입력하면 해당 id에 대한 엔티티를 DB에서 가져온다.

- 해당 엔티티를 저장한 엔티티와 값을 비교한다.

 

- PostControllerTest.java

.......

@Test
    @DisplayName("글 1개 조회")
    void test4() throws Exception {
        //given
        Post post = Post.builder()
                .title("name")
                .content("baekinsoo")
                .build();
        postRepository.save(post);
        //expected
        mockMvc.perform(MockMvcRequestBuilders.get("/posts/{postId}", post.getId())
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(jsonPath("$.id").value(post.getId()))
                .andExpect(jsonPath("$.title").value("name"))
                .andExpect(jsonPath("$.content").value("baekinsoo"))
                .andDo(MockMvcResultHandlers.print());
    }
}


- 클래스 분리

- 클라이언트 측에서 title의 글자를 입력에 상관없이 10글자로 제한 해달라는 요청이 오게되면 어떻게 해야 할까?

 

- Post.java

.....

public String getTitle() {
        return this.title.substring(0, 10);
    }
    .....

- 위와 같이 엔티티의 getter에 조건에 맞게 코드를 작성하면 쉽게 해결할 수 있다.

 

- PostControllerTest.java

@Test
    @DisplayName("글 1개 조회")
    void test4() throws Exception {
        //given
        Post post = Post.builder()
                .title("123456789012345")
                .content("insoo")
                .build();
        postRepository.save(post);

        //클라이언트 요구사항
        //json 응답에서 title값 길이를 최대 10글자로 해주세요.

        //expected
        mockMvc.perform(MockMvcRequestBuilders.get("/posts/{postId}", post.getId())
                        .contentType(APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(post.getId()))
                .andExpect(jsonPath("$.title").value("1234567890"))
                .andExpect(jsonPath("$.content").value("insoo"))
                .andDo(print());
    }
}

- 원하는 10글자 제한의 기대값이 응답된 것을 확인할 수 있다.

 

*** 하지만 위 경우의 문제점은 Post 엔티티를 사용하는 모든 로직에 영향을 받기 때문에 클라이언트 요청사항(title 10글자 제한)이 제한적이지 않게 된다.

- 즉, 엔티티에는 절대 서비스 정책(로직)을 넣지 않는 것이 좋다.


- 클래스 분리

- 위와 같이 Service 로직에 있어 별도의 요청이 들어오게 되면 해당 요청을 위한 응답 클래스를 별도로 생성하는 것이 좋다.

 

- PostResponse.java

@Getter
@RequiredArgsConstructor
public class PostResponse {

    private Long id;
    private String title;
    private String content;

    @Builder
    public PostResponse(Long id, String title, String content) {
        this.id = id;
        this.title = title.substring(0, Math.min(title.length(), 10));
        this.content = content;
    }
}

- 클라이언트 요청에 맞는 응답 클래스를 생성한다.

- 위의 클래스는 클라이언트 요청을 위한 클래스이기 때문에 클라이언트의 요청이 해당하는 부분에 사용하면 된다.

this.title = title.substring(0, Math.min(title.length(), 10));

- 그냥 substring을 사용하게 되면, 10글자가 안되는 값이 들어오면, 에러가 발생하기 때문에 Math.min(title.length(), 10)를 통해 10글자가 아니더라도 작성된 글자수 만큼 맞출 수 있다.

 

- PostService.java

.......

public PostResponse get(Long postId) {
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 글입니다."));

        PostResponse postResponse = PostResponse.builder()
                .id(post.getId())
                .title(post.getTitle())
                .content(post.getContent())
                .build();

        return postResponse;
    }
}

- Service의 get() 메서드에서 id값을 통해 받아온 post 엔티티를 클라이언트 요청에 맞는 객체로 변환하기 위해서 PostResponse 객체에 넣어주는 작업을 한다.

- 즉, Post로 받은 객체를 PostResponse 객체로 변환해서 반환하게 되면 요청에 맞는 객체를 전달 할 수 있다.

 

- PostController.java

.......

@GetMapping("/posts/{postId}")
    public PostResponse get(@PathVariable Long postId) {
        PostResponse postResponse = postService.get(postId);
        return postResponse;
    }
}

- Controller도 마찬가지로 응답 데이터로 PostResponse를 반환한다.

 

- PostControllerTest.java

@Test
    @DisplayName("글 1개 조회")
    void test4() throws Exception {
        //given
        Post post = Post.builder()
                .title("This is title")
                .content("baekinsoo")
                .build();
        postRepository.save(post);
        //expected
        mockMvc.perform(MockMvcRequestBuilders.get("/posts/{postId}", post.getId())
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(jsonPath("$.id").value(post.getId()))
                .andExpect(jsonPath("$.title").value("This is ti"))
                .andExpect(jsonPath("$.content").value("baekinsoo"))
                .andDo(MockMvcResultHandlers.print());
    }
}

- 위와 같이 엔티티를 직접 사용하지 않고 원하는 요청에 대한 값을 응답으로 보내는 것을 확인 할 수 있다.

728x90