- 게시글 단건 조회
- 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());
}
}
- 위와 같이 엔티티를 직접 사용하지 않고 원하는 요청에 대한 값을 응답으로 보내는 것을 확인 할 수 있다.
'Programming > Spring' 카테고리의 다른 글
Spring - 페이징 (Jpa, Querydsl) (0) | 2023.08.31 |
---|---|
Spring - 게시글 조회 (다건 조회, 임시 H2 활용한 실제 서비스 확인) (0) | 2023.08.30 |
Spring - 클래스 분리, 데이터 저장 (Builder, ObjectMapper, 상황 별Controller 반환 값 등...) (0) | 2023.08.29 |
Spring - 데이터 검증(컨트롤러, 데이터 타입, @ControllerAdvice...) (0) | 2023.08.28 |
JPA - 기본 동작 방식, 아키텍처 (0) | 2023.08.01 |