Entity 올바른 수정은 무엇인가 ?
변경 감지(Dirty Checking 방식)
스프링에서 EntityManger의 주요 기능 중 하나로 Dirty Check가 있다.
EntityManger는 현재 트랜잭션에서 영속성 컨텍스트에 포함되어 있는 Entity객체에 수정이 발생할 경우, 트랜잭션이 커밋되는 시점에 내부적으로 UPDATE 쿼리를 생성하여 전달한다.
기본적으로 EntityManager의 find()나 createQuery() 메서드를 통해 조회는 Entity 객체는 영속성 컨텍스트에 포함된다. 따라서, persist() 메서드를 명시적으로 호출하지 않아도 된다.
그러나, EntityManager는 준영속 상태(Detached)의 Entity에 대해서는 Dirty Check를 수행하지 않는다. 만약, 사용자가 폼 정보를 기반으로 새로운 Entity 객체를 생성한다면 해당 객체는 준영속 상태에 해당되어 Dirty Check가 수행되지 않는다.
그러므로 준영속 상태에 놓인 Entity 객체를 수정하기 위해서는 EntityManager에서 find()나 createQuery()를 호출하여 영속 상태의 객체를 불러와 데이터를 수정하거나, 해당 Entity를 merge하여 영속 상태로 변경하여 Dirty Check가 작동하도록 해야한다.
merge() 방식
== 병합 사용
merge의 동작 방식은 다음과 같다.
- 1차캐시 엔티티에서 해당 엔티티를 찾는다.
- 여기서 찾지 못하면 DB에서 엔티티를 검색한다.
- DB에서 가져온 엔티티의 값에 준영속 상태의 값들을 하나씩 채워넣는다.
- 이렇게 채워넣은 엔티티를 return한다.
정확히 말하자면, 준영속화 되어 있는 엔티티를 영속화 해주는게 아니라, 영속화 된 새로운 엔티티에 값을 끼워넣어 주는 것이다.
이 방식에는 한 가지 큰 문제점이 있다.
바로 준영속 상태의 값들 중 null인 값이 있다면 기존의 데이터에서도 그게 null이 된다는 것!!
예를 들어
이름,나이,키민우 | 25 | 182 |
으로 이미 저장되어 있고, 이 사람이 키가 커서 183이 되었다고 가정했을 때
키만 183으로 설정한 엔티티를 merge하면
이름,나이,키null | null | 183 |
이 될 수도 있다는 것이다!!
예시
다음은 준영속 상태의 Entity를 변경감지를 통해 업데이트 하는 코드이다.
@Service
@AllArgsConstructor
public class ItemService {
ItemRepository itemRepository;
@Transactional
public Item updateItem(Long itemId, Item param) {
// findResult 객체는 영속 상태에 있으므로 변경감지가 동작한다
Item findResult = itemRepository.findOne(itemId).get();
// param은 준영속 상태의 Entity 객체이다
findResult.setName(param.getName())
findResult.setPrice(param.getPrice())
// param은 여전히 준영속 상태이다.
// 하지만, 반환되는 findResult는 영속 상태의 엔티티이다.
return findResult
}
}
결론: 변경 및 삭제는 Transactional 변경감지를 이용해야 하는것이 가장 효율적이다.
조회는 Transactional(readonly=True)를 사용하자 !