Spring & SpringBoot

실전 스프링부트와 JPA활용1 - 변경 감지와 병합(merge)

땅콩콩 2024. 1. 8. 19:10
준영속 엔티티를 수정하는 두가지 방법

 

영속성 컨텍스트가 더는 관리하지 않는 엔티티를 준영속엔티티라 한다.

쉽게 말해서 DB에 이미 한번 갔다와서 id와 같은 식별자가 이미 존재하는 엔티티를 뜻한다.

그리고 이러한 준영속 엔티티를 수정하는데에는 2가지 방법이 있다. 

 

1. 변경 감지 기능 사용(Dirty Checking)

 

영속성 컨텍스트 안에서 엔티티를 다시 조회하고, 그 후에 데이터를 수정하는 방법이다.

 

예를 들어 같은 트랜잭션 안에서 엔티티의 값이 수정이 되고 해당 트랜잭션이 커밋되면,

트랜잭션이 커밋되는 그 순간에 update 쿼리가 나간다. (em.flush > 영속성 컨텍스트에서 바뀐 엔티티들을 다 찾아서 update함.)

그리고 이것을 dirty checking, 즉 변경감지가 동작한다고 한다.

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
	Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다. -> 이제 이 엔티티는 영속상태!
	findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 
  }

 

 

2. 병합 사용(Merge)

 

준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 방법이다.

 

DB에서 식별자를 통해 엔티티를 찾아와서 영속 상태로 바꾸고, 해당 엔티티의 모든 속성을 update한다.

그래서 트랜잭션이 커밋될 때 해당 변경사항들이 반영된다.

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 
	Item mergeItem = em.merge(itemParam);
  }

 

예를 들어 준영속 상태의 엔티티인 member의 값을 merge를 통해 변경하는 과정을 살펴보자.

 

merge()를 실행하는 순간, 우선 member의 id를 가지고 영속성 컨텍스트의 1차 캐시에서 조회한다.

그리고 1차 캐시에 해당 엔티티가 없을 경우 DB에서 엔티티를 조회해서 이걸 1차 캐시에 저장한다. (mergeMember)

 

조회된 영속 엔티티(mergeMember)에 member 엔티티의 모든 값을 전부 채워 넣는다.

이 과정에서 목표로 했던 데이터의 변경이 일어난다.

 

이후 영속성 컨텍스트는 영속 상태인 mergeMember를 반환하고,

트랜잭션이 커밋되는 시점에 변경감지 기능을 통해 DB에 update 쿼리가 나가게 된다.

 

위 두 방법의 차이는 부분 교체 vs 전체 교체 이다.

변경감지 기능을 사용하면 원하는 속성만 선택적으로 변경할 수 있지만, 병합을 사용하면 위에서 확인했듯이 모든 속성이 변경된다.

또한 병합은 모든 속성을 교체하기 때문에 병합시에 값이 없으면 null로 업데이트를 할 위험도 있다.

 

따라서 실무에서 엔티티를 변경할 때는 항상 변경감지 기능을 사용하는 것이 바람직하다.

 

즉, 컨트롤러에서 엔티티를 어설프게 생성하지 말고,

트랜잭션이 걸려있는 서비스 계층에서 영속 상태에 있는 엔티티를 조회해서 데이터 수정이 필요한 부분만 직접 변경하라는 것이다.

 

이렇게 하면 엔티티의 전체 값을 변경할 필요도 없고, 트랜잭션이 커밋되는 순간에 변경감지가 일어나 update쿼리가 반영되므로

의도했던 대로만 엔티티의 값을 수정할 수 있다.