요구사항이 단순할 때는 dto 대신 엔티티를 바로 사용해도 된다.
하지만 요구사항이 복잡한 경우 엔티티를 바로 사용하면 엔티티에 화면 관련 기능이 추가되고 화면 종속적이게 된다.
따라서 유지보수가 어려워진다.
JPA를 사용할 때는 엔티티를 다른 곳에 의존성이 없도록 순수하게 설계하는 것이 중요하다.
API를 개발할 때는 절대 엔티티를 반환하면 안된다.
엔티티의 필드를 그대로 반환하는 문제가 있고, API 스펙이 변경된다는 단점이 있다.
엔티티는 API 외부로 호출하면 안된다. dto를 활용하는 것이 좋다.
@PostMapping("items/{itemId}/edit")
public String updateItem(@PathVariable String itemId, @ModelAttribute("form") BookForm form){
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
수정에는 merge가 아닌 변경감지를 사용하라는 것이 JPA의 가이드이다.
트랜잭션 내에서 엔티티를 find 하고 값을 수정하고 트랜잭션이 commit되면 변경감지(dirty checking)을 통해 알아서 값이 저장된다.
준영속 엔티티는 JPA의 영속성 컨텍스트가 더이상 관리하지 않는 엔티티를 말한다.
엔티티를 업데이트 하기 위해 새로운 엔티티를 만드는 경우, 새로운 엔티티는 id값을 갖고 있다(그 id 값은 db에 있다) 이런 것을 준영속 엔티티라 한다.
위의 코드에서 Book이 바로 준영속 엔티티가 된다.
준영속 엔티티는 이미 한번 db에 저장되었기 때문에 기존의 식별자를 갖는 준영속 엔티티이다.
준영속 엔티티는 JPA가 관리하지 않는다. 영속 엔티티는 변경감지가 발생하지만 준영속 엔티티는 변경감지가 발생하지 않는다(JPA가 관리하지 않기 때문에)
이런 경우 트랜잭션에 commit이 발생해도 update가 일어나지 않는다.
그렇다면 준영속 상태의 엔티티는 어떻게 데이터를 변경할 수 있을까?
변경 감지 기능 사용 방법은 먼저 db에서 엔티티를 찾는다. db에서 찾았으므로 영속 상태 엔티티이고 해당 엔티티를 변경하면 변경 감지를 사용할 수 있다.
이런 경우 itemRepository.save(변경된엔티티)
를 할 필요가 없다.
이 엔티티는 영속상태이므로 트랜잭션의 commit이 발생할 때 변경감지에 의해 알아서 update 쿼리가 날라가서 변경된다.
이 방법이 보통 더 나은 방법이다.
merge는 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 방식이다.
db에서 merge(파라미터)
에서 파라미터로 넘어온 id로 엔티티를 찾아서 해당 엔티티의 모든 값을 파라미터의 값으로 바꾼다.
구체적인 동작 방식은 다음과 같다.
반환하는 mergeMember는 영속 상태이지만 파라미터로 넘어온 준영속 엔티티는 계속 준영속 상태라는 것이다.
따라서 반환된 엔티티를 이후 코드에서 활용해야 한다.
변경 감지 기능을 사용하면 원하는 속성만 가지고 값을 변경할 수 있지만 병합은 모든 값을 변경해버린다는 주의점이 있다.
이것이 위험한 이유는 병합 시 파라미터에 값이 없으면 null로 업데이트를 해버리기 때문이다
따라서 엔티티를 변경할 때는 항상 변경감지를 사용하자!
그리고 변경할 때는 setter로 다 변경하지 말고 변경하는 메소드를 따로 만드는 것이 좋다.
그래야 유지보수가 쉽고 역추적하기 좋다.
컨트롤러에서 어설프게 엔티티를 생성하지 말자
트랜잭션이 있는 서비스 계층에서 식별자와 변경할 데이터를 명확하게 전달(dto로) 해라.
트랜잭션이 있는 서비스 계층에서 엔티티를 조회(영속상태) 해서 변경감지를 통해 변경하자
트랜잭션 커밋 시점에 변경 감지가 실행된다.