Spring-boot

스프링 부트에서 JPA로 데이터베이스 다루기-3

Oxix 2022. 7. 5. 02:22

이전까지는 랜덤 포트 실행 및 insert 쿼리 실행을 해보았다. 등록 기능을 완성 했으니 수정/ 조회 기능을 해볼 차례이다.

PostsApiController

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService service;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto) {
        return service.save(requestDto);
    }

    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto) {
        return service.update(id, requestDto);
    }

    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById(@PathVariable Long id) {
        return service.findById(id);
    }
}

PostsResonseDto

@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를 받아 필드에 값을 넣는다. 모든 필드를 가진 생성자가 필요하지 않으므로 Dto는 Entity를 받아 처리한다.

PostsUpdateRequestDto

@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
    private String title;
    private String content;

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

PostsService

@RequiredArgsConstructor
@Service
public class PostsService {

    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }

    @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 posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id = " + id));
        return new PostsResponseDto(posts);
    }

}

update 기능에서 데이터베이스에 쿼리를 날리는 부분이 없다. 이유는 JPA의 영속성 컨텍스트 때문이다. 엔티티를 영구 저장하는 환경이므로 일종의 논리적 개념이라고 생각하면 된다. JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다.

JPA의 엔티티 매니저가 활성화된 상태로 트랜잭션 안에서 데이터베이스에서 데이터를 가져오면 해당 데이터는 영속성 컨텍스트가 유지된 상태이다. 이 상태에서 해당 데이터의 값을 변경하면 트랜잭션이 끝나는 시점에 해당 테이블에 변경분을 반영한다. 즉, Entity 객체의 값만 변경하면 별도로 Update 쿼리를 날릴 필요가 없다는 것이다. 해당 개념을 더티 체킹 이라고 한다.

테스트 코드를 등록후 실행하면 update 쿼리가 실행되는 것을 확인할 수 있다. 

PostsApiControllerTest

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @After
    public void tearDown() throws Exception {
        postsRepository.deleteAll();
    }

    @Test
    public void createPosts() throws Exception {
        String title = "타이틀";
        String content = "콘텐츠";
        PostsSaveRequestDto dto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts";

        //when
        ResponseEntity entity = restTemplate.postForEntity(url, dto, Long.class);

        //then
        assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(entity.getBody()).isGreaterThan(0L);

        List all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);

    }

    @Test
    public void update() throws  Exception {
        Posts savePosts = postsRepository.save(Posts.builder()
                .title("타이틀")
                .content("코온텐츠으")
                .author("authoooor")
                .build());

        Long updateId = savePosts.getId();
        String expectedTitle = "title2";
        String expectedContent = "content2";

        PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
                .title(expectedTitle)
                .content(expectedContent)
                .build();
        String url = "http://localhost:"+port+"/api/v1/posts/"+updateId;
        HttpEntity requestEntity = new HttpEntity<>(requestDto);

        //when
        ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.PUT,requestEntity,Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(expectedTitle);
        assertThat(all.get(0).getContent()).isEqualTo(expectedContent);
    }

}

실행결과 

기본적인 등록/수정/조회 기능을 구현했고 테스트했다. 등록/수정은 테스트 코드로 보호해 주고 있어 변경 사항이 있어도 안전하게 변경 가능하다.