외부 엔티티 사용과 준영속 상태
애플리케이션을 작성하다보면 외부에서 엔티티에 필요한 정보를 모두 받아오도록 작성하는 경우가 있다.
@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:/tiems";
}
Java
복사
이와 같이 받아온 데이터를 기준으로 새로 Item을 생성하는 식으로 작성을 하게 되면, 생성된 객체는 JPA에 전혀 등록되지 않은 준영속 상태이다.
일반적으로 엔티티를 수정하는 경우 영속성 컨텍스트에서 트랜잭션이 끝나는 시점에 변경 감지를 수행하여 다르다면 update 쿼리를 작성한다. 하지만 이러한 준영속 상태의 엔티티를 수정한 경우, 영속성 컨텍스트에서 관리되고 있지 않기 때문에 변경감지를 통해 엔티티를 수정할 수 없다.
준영속 상태의 엔티티 저장하기
때문에 위 코드의 새로 생성된 엔티티를 DB에 반영하는 방법은 변경 감지를 통한 방법과 병합을 통한 방법으로 2가지가 있다.
변경 감지로 저장하기
@Transactional
public void updateItem(Long itemId, Item item) {
// 영속성 컨텍스트에서 엔티티를 다시 조회
Item findItem = itemRepository.findOne(itemId);
// 영속화된 엔티티의 데이터를 수정
findItem.setName(item.getName());
findItem.setPrice(item.getPrice());
findItem.setStockQuantity(item.getStockQuantity());
}
Java
복사
위와 같이 주어진 식 별르를 통해해시 회조하여, 영속성 컨텍스트에서 관리되는 영속화된 엔티티를 가져와 수정하는 방식이다. 영속화된 엔티티의 값을 수정해두면, 트랜잭션이 종료되는 시점에 영속성 컨텍스트에서 변경 감지 후 flush하여 자동으로 update 쿼리가 실행된다.
병합으로 저장하기
@Transactional
public void updateItem(Item item) {
Item mergeItem = em.merge(item);
}
Java
복사
병합은 이와 같이 준영속 상태의 엔티티를 영속성 컨텍스트에 등록하여 다시 영속화하는 방법이다.
병합은 이처럼 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회하고, 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체(병합)한다. 이후 영속화된 채로 관리하다가, 트랜잭션 커밋 시점에 변경 감지 기능으로 update 쿼리가 실행된다.
병합 방식의 문제점
병합 방식을 사용하면 영속성 컨텍스트에서 영속화된 엔티티를 조회 이후, 영속화 엔티티의 모든 필드를 병합하기 위해 넣은 엔티티의 모든 값으로 대체해버린다. 때문에 모든 필드값이 내가 바꾸고자 하는 필드와 일치하지 않는 경우 문제가 발생한다. 수정 로직에서 예를 들어 수정하지 않는 값은 null을 넣어서 엔티티를 받아오게 되면, 이를 병합하는 경우 null 값이 그대로 데이터베이스에 저장되게 된다.
병합 로직 자체는 우리가 직접 엔티티를 조회하여 영속화 후 영속화된 엔티티의 값을 수정하는 것과 다를 바 없어 성능상으로도 큰 이점이 없다. 오히려 위와 같은 위험성만 내포하고 있기 때문에 가급적 병합 방식은 사용하지 말고, 변경 감지 방식으로 사용하자.