본문 바로가기

[JPA] 변경 감지와 병합(merge)

@cayman0312025. 12. 2. 11:42

변경 감지와 병합을 알아보기 전에 먼저 준영속 엔티티부터 알아보도록 하자. 준영속 엔티티란 영속성 컨텍스트가 더 이상 관리하지 않는 엔티티를 의미한다.
즉, DB에 이미 한번 저장되어 식별자가 존재하는 엔티티이다. 만약 임의로 엔티티를 만들어 내더라도 이렇게 기존 식별자를 가지고 있다면 준영속 엔티티이다.

이러한 준영속 엔티티를 수정하는 방법엔 변경 감지 기능을 사용하는 방법과 병합 기능을 사용하는 방법이 있다.

변경 감지

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티

    Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
    findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.
}

변경 감지 기능을 사용하는 방법은 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법이다.

트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 -> 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작하여 DB에 sql문 실행.

병합 사용

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

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

병합 동작 과정

  1. merge()를 실행한다.
  2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
    • 만약 1차 캐시에 엔티티가 없다면 db에서 엔티티를 조회하고 1차 캐시에 저장한다.
  3. 조회한 영속 엔티티(mergeMember)에 member 엔티티의 값을 채운다.(member 엔티티의 모든 값을 mergeMember에 밀어넣는다.)
  4. 영속 상태인 mergeMember을 반환한다.

이러한 과정을 다시한번 정리하자면 다음과 같다.

  1. 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회
  2. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체(병합)
  3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작하여 db에 sql 실행

변경 감지 기능은 원하는 속성만 선택하여 변경 가능하지만, 병합은 모든 속성을 변경 하기 때문에 병합시 값이 없으면 null값을 삽입할 위험이 있다. (사용주의!)

병합 사용시 주의점

실무에서는 보통 업데이트의 기능이 제한적이지만 병합을 사용하면 모든 필드를 변경해버리고, 데이터가 없다면 null값으로 업데이트를 한다. 따라서 병합을 사용하며 이 문제를 해결하려면 변경 폼 화면에서 모든 값을 유지해야 한다. 그러나 이러한 방식은 변경가능한 데이터만 노출하는 실무 환경과 맞지 않기 때문에 더 불편할 수 있다.

해결방법

엔티티를 변경할 때는 병합 말고 항상 변경 감지를 사용하는 것이 좋다.

  • 컨트롤러에 엔티티를 생성하지 않는다.
  • 트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달한다.(파라미터나 dto를 이용하는것이 좋다.)
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 데이터를 직접 변경한다.

출처: 김영한 스프링 JPA 강의

'Develop > Spring' 카테고리의 다른 글

[Spring] 코드 리팩토링 - 계층 분리  (1) 2025.12.17
[JPA] 회원 도메인  (0) 2025.12.02
[JPA] 도메인 분석 설계  (0) 2025.12.02
[MVC] 요청 매핑  (0) 2025.12.02
[MVC] 핸들러 매핑과 핸들러 어댑터  (0) 2025.12.02
cayman031
@cayman031 :: 그누로그

목차