Spring/JPA
[JPA] 변경 감지와 병합(merge) ,데이터수정
김긍수
2021. 3. 22. 20:05
변경감지와 병합의 차이는 정말 중요한 내용이므로 꼭 이해해야한다.
준영속 엔티티?
영속성 컨텍스트가 더 이상 관리하지 않는 엔티티를 말한다. (Book 객체는 이미 DB에 한번 저장되어서 식별자가 존재한다. 이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속엔티티라고 볼 수 있다.)
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());
준영속의 문제
- JPA가 관리를 안함. 그러므로 아무리 값을 바꿔도 업데이트 쿼리가 실행되지 않는다.
준영속 엔티티를 수정하는 방법
- 변경 감지 기능 사용 (Dirty Checking)
- 병합(merge) 사용
변경감지 (DirtyChecking)를 사용하는 방법
영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법
트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 -> 트랜잭션 커밋 시점에서 변경 감지
이 동작을 해서 DB에 업데이트 SQL을 실행한다.
@Transactional
public void updateItem(Long itemId, Book param) { //param : 파리미터로 넘어온 준영속 상태의 엔티티
Item findItem = itemRepository.findOne(itemId); // itemId를 기반으로 DB에 있는 실제 영속상태의 엔티티를 가져옴.
findItem.setPrice(param.getPrice());
findItem.setName(param.getName());
findItem.setStockQuantity(param.getStockQuantity());
findItem.setPrice(param.getPrice());
/*
-변경감지-
itemRepository.save(findItem); <- 이런 걸 호출할 필요가 없다.
왜냐하면 findItem은 영속상태이다.
값 셋팅이 완료된 후 트랜잭션에 의해 커밋이 된다.
커밋이 되면서 JPA는 flush를 날린다. (영속성 컨텍스트에 있는 엔티티 중 변경된 것을 찾는다.)
*/
}
Book book = em.find(Book.class, 1L);
book.setName("변경이름");
//변경감지
//commit
트랜잭션이 커밋하면 변경한 것을 JPA가 찾아서 업데이트 쿼리를 자동으로 생성하여 DB에 반영하는 것이다.
병합 (Merge)을 사용하는 방법(최대한 사용하지 않기)
준영속 상태의 엔티티를 영속 상태로 변경할 때 사용한다.
@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
Item mergeItem = em.merge(item);
}

병합 동작 방식
- merge()를 실행한다.
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
- 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다.
- 조회한 영속 엔티티(mergerMember)에 member엔티티의 값을 채워 넣는다. (member 엔티티의 모든 값을 mergeMember에 밀어넣는다. 이때 mergeMember의 "회원1"이라는 이름이 "회원명변경"으로 바뀐다.)
- 영속 상태인 mergeMember를 반환한다.
병합시 동작 방식을 간단히 정리
- 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
- 영속 엔티티의 값을 준 영속 엔티티의 값으로 모두 교체한다.(병합)
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 DB에 업데이트 SQL이 실행된다.
@Transactional
public Item updateItem(Long itemId, Book param) {
Item findItem = itemRepository.findOne(itemId); // itemId를 기반으로 DB에 있는 실제 영속상태의 엔티티를 가져옴.
findItem.setPrice(param.getPrice());
findItem.setName(param.getName());
findItem.setStockQuantity(param.getStockQuantity());
return findItem; // merge
// 이렇게 세터로 하지말고, 메소드를 만드는 것이 좋다.
/*
-변경감지- 반환값 void
itemRepository.save(findItem); <- 이런 걸 호출할 필요가 없다.
왜냐하면 findItem은 영속상태이다.
값 셋팅이 완료된 후 트랜잭션에 의해 커밋이 된다.
커밋이 되면서 JPA는 flush를 날린다. (영속성 컨텍스트에 있는 엔티티 중 변경된 것을 찾아서 있다면 업데이트쿼리를 날린다.)
*/
}
주의 : 변경 감지기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
병합 시 값이 없으면 null로 업데이트될 수 있다.
따라서 병합보다는 변경감지기능을 사용하는 것이 좋다. 최대한 Merge는 쓰지 않는게 좋다.