API를 만들기 위해서는 총 3개의 클래스가 필요하다
Service만 비즈니스 로직을 처리해야 한다는 것이 아니라 Service는 트랜잭션,도메인 간 순서 보장의 역할만 한다
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
@Transactional
public Long save(PostsSaveRequestDto requestDto){
return postsRepository.save(requestDto.toEntity()).getId();
}
}
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostsService postsService;
@PostMapping("/api/v1/posts")
public Long save(@RequestBody PostsSaveRequestDto requestDto){
return postsService.save(requestDto);
}
}
생성자로 Bean객체를 받도록 하면 @Autowired와 동일한 효과를 볼 수 있다. @RequiredArgsConstructor에서 final이 선언된 모든 필드를 인자값으로 하는 생성자를 대신 생성해준다.
@NoArgsConstructor은 해당 클래스에 매개변수 없는 기본 생성자를 생성합니다.
@AllArgsConstructor은 이 생성자는 객체를 생성할 때 모든 필드 값을 인자로 받아서 객체를 초기화합니다. 이를 통해 객체를 생성할 때 모든 필드 값을 설정할 수 있습니다.
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private PostsRepository postsRepository;
@AfterEach
public void tearDown() throws Exception{
postsRepository.deleteAll();
}
@Test
public void Posts_등록된다() throws Exception {
//given
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.author("author")
.build();
String url = "http://localhost:" + port +"/api/v1/posts";
//when
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url,requestDto,Long.class);
//then
org.assertj.core.api.Assertions.assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
org.assertj.core.api.Assertions.assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all = postsRepository.findAll();
org.assertj.core.api.Assertions.assertThat(all.get(0).getTitle()).isEqualTo(title);
org.assertj.core.api.Assertions.assertThat(all.get(0).getContent()).isEqualTo(content);
}
}
이 책에 있는 Dto를 따라 작성하면서 항상 Response에 대한 Dto를 제대로 안짜고 있었다는 것을 알게되었다
@Getter
public class PostsResponseDto {
private Long id;
private String title;
private String content;
private String author;
public PostsResponseDto(Posts entity){
this.id=entity.getId();
this.title = entity.getTitle();
this.content = entity.getContent();
this.author = entity.getAuthor();
}
}
이 PostsResponseDto는 Entity의 필드 중 일부만 사용하므로 생성자로 Entity를 받아 필드에 값을 넣습니다
@Transactional
public Long update(Long id, PostsUpdateRequestDto requestDto){
Posts posts = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id="+ id));
posts.update(requestDto.getTitle(), requestDto.getContent());
return id;
}
public PostsResponseDto findById (Long id){
Posts entity = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id="+ id));
return new PostsResponseDto(entity);
}
update Service에는 데이터베이스에 쿼리를 날리는 부분이 없다. 이게 가능한 이유는 JPA의 영속성 컨텍스트 때문이다.
영속성 컨텍스트는 엔티티를 영구 저장하는 환경이다.
결과:

