[Spring JPA] 변경감지 vs 병합

변경 감지와 병합

 

 영속성 컨텍스트가 더 이상 관리하지 않는 엔티티, 즉 식별자(id)를 기준으로 영속상태가 되어 DB에 저장되었던 적이 있지만 현재는 아닌 객체를 준영속 상태라고 한다. JPA가 관리하고 있는, 영속성 상태 객체의 변경감지는 transaction이 commit될때 작동한다. 하지만 준영속 상태의 엔티티는 JPA가 관리하지 않아서 변경 감지가 일어나지 않는다. 그래서 단순히 java 객체의 상태를 업데이트 하는 것만으로는 갱신이 일어나지 않는다. 

 

 이러한 준영속 상태의 엔티티를 변경하기 위해서는 변경 감지(Dirty Checking) 기능을 사용하거나, 병합(merge)를 사용할 수도 있다.

 

 

변경 감지

 

@Transactional
public void updateItem(Item itemParam) {
    Item findItem = em.find(Item.class, itemParam.getId());
    
    findItem.setPrice(itempParam.getPrice());
    .
    .
}

 

위 메소드는 itemParam으로 넘어온 준영속 상태의 엔티티를 영속성 콘텍스트에서 영속성 상태의 엔티티를 id값을 이용한 조회를 통해 영속상태의 엔티티를 얻은 후, 데이터를 수정하고 있다. 위와 같이 준영속 상태의 엔티티를 이용해서 영속상태의 엔티티에 접근하여 변경 감지 기능을 이용하여 수정을 할 수도 있다.

 

 

병합

 

병합은 준영속상태의 엔티티를 영속 상태로 변경하여 수정하는 방법이다.

@Transactional
public void updateItem(Item itemParam) {
    Item mergeItem = em.merge(itemParam);
}

 

 병합하기 위해서 위와 같이 준영속 상태의 엔티티를 EntityManager.merge();에 인자로 넣었다.

 

 merge()함수는 파라미터로 넘어온 준영속 엔티티의 식별자(id)값을 이용하여 1차 캐시에서 엔티티를 조회한다. (1차캐시에 데이터가 없다면 DB로 접근하여 조회하여 1차캐시에 저장한다.) 영속 엔티티를 조회했다면 그 영속 엔티티의 값을 인자로 넘어온 준영속 상태의 엔티티의 값으로 교체(병합)하고 값의 변경이 이루어진 영속 엔티티를 반환한다. 그러면 이제는 영속 엔티티에 변화가 일어났기 때문에 트랜잭션의 커밋 시점에 변경 감지가 동작하여 update문이 나간다.

 

ㆍ병합 또한 결론적으로는 변경 감지를 이용한 방법이다. 하지만 일일히 값을 수정하는 변경 감지 방법과 다르게 merge함수 내에서 값을 수정해주는 코드를 자동으로 작성해준다.

 

ㆍ하지만, merge는 로직상 변경 감지와 다르게 모든 속성이 변경이 강제된다. 그래서 일부 속성만 변경이 이루어질 경우 본래 값이 사라지고 null값이 DB에 저장될 위험성이 있다. 

 

 

 

결론

 병합을 이용한 수정은 변경감지 코드를 직접 작성하지 않아서 번거로움이 덜하고 사용하기 용이하다. 하지만 병합을 이용할 경우 영속 엔티티의 모든 속성을 강제로 업데이트시키기 때문에 선택적인 수정이 불가능하고(같은 값이 들어가더라도 동일한 값으로 수정해주어야 한다), 일부만 업데이트할 경우 DB에 null값이 들어가는 등 오류가 발생할 수 있다.

 

 일단 컨트롤러에서 어설프게 엔티티를 생성하지 말자. merge함수의 사용은 최대한 지양하는 방향으로 잡고, 변경 감지를 사용하여 서비스 계층에서 트랜잭션 내에서 영속 상태의 엔티티를 직접 조회하고 변경해주자.

(위의 예시처럼 setter를 이용한 접근이 아니라, 변경 함수를 별도로 작성하면 더욱 좋다)