JPA

JpaRepository의 save() 메서드 내부동작 - Persist vs Merge

1. 알고자 하는 것

- JpaRepository의 save() 메서드의 동작에 대해 내부 코드를 확인한다.

- 내부 코드 중 persist와 merge의 차이를 테스트코드로 확인한다.

 

2. 알게된 것

JpaRepository의 save 메서드 내부 확인

- 일반적으로 JpaRepository는 다음과 같이 인터페이스 형식으로 상속받아 편리하게 기본 CRUD 메서드를 사용한다.

public interface MemberRepository extends JpaRepository<Member, Long> {
}

- 이 때, JpaRepository 인터페이스를 구현한 SimpleJpaRepository를 target으로 가지는 Proxy가 Bean으로 등록된다.

- 실제 CRUD 동작은 SimpleJpaRepository가 수행하는 것이다.

- SimpleRepository에 구현된 save 메서드를 확인하면 다음과 같다.

@Transactional
@Override
public <S extends T> S save(S entity) {

   Assert.notNull(entity, "Entity must not be null");

   if (entityInformation.isNew(entity)) {
      em.persist(entity);
      return entity;
   } else {
      return em.merge(entity);
   }
}

- EntityInformation.isNew 메서드를 통해 인자로 전달받은 Entity가 새로 등록되는 Entity라면 persist를 수행한다.

- 기존에 존재하는 Entity라면 merge를 수행한다.

 

persist와 merge의 차이

- persist는 비영속(New) 상태의 Entity를 영속(Persist) 상태로 만드는 동작이다.

- 즉, 영속성 컨텍스트와 전혀 관계를 맺지 않고 있던 새로운 Entity를 영속성 컨텍스트에 저장하여 관리될 수 있도록 한다.

- merge는 기존에 영속성 컨텍스트에 의해 관리되다가 현재는 분리되어 관리되지 않는 준영속(Detach) 상태의 Entity를 다시 영속(Persist) 상태로 만드는 동작이다.

- persist : 비영속(New) -> 영속(Persist)

- merge : 준영속(Detach) -> 영속(Persist)

- persist, merge 둘 다 Entity를 영속상태로 만드는 동작이다.

- 차이점은 대상 Entity가 아예 새로 만들어지는 Entity인지, 기존에 영속성 컨텍스트에 의해 관리되던 Entity인지이다.

- persist와 merge의 차이를 테스트코드로 확인한다.

@Test
void saveWithPersistAndMergeTest() {
    Member member1 = new Member("member", 10); // member1 (New)
    Member saveMember1 = memberRepository.save(member1); // Persist (New -> Persist)
    assertThat(em.contains(member1)).isTrue(); // member1(persist) : 관리됨 
    assertThat(em.contains(saveMember1)).isTrue(); // saveMember1(persist 반환 Entity) : 관리됨

    Member member2 = new Member("member2", 20); // member2 (Detach)
    member2.setId(member1.getId()); 
    Member saveMember2 = memberRepository.save(member2); // Merge (Detach -> Persist)
    assertThat(em.contains(member2)).isFalse(); // member2(Detach) : 관리되지 않음
    assertThat(em.contains(saveMember2)).isTrue(); // saveMember2(merge 반환 Entity) : 관리됨
    assertThat(saveMember2.getName()).isEqualTo(member2.getName()); // merge 되어 값 변경됨
}

 

[member1, saveMember1]

- member1은 최초로 생성된 Entity로, 비영속(New) 상태이다.

- 비영속 상태의 member1에 대해 save를 수행하면 persist가 수행되고, save가 수행된 Entity(saveMember1)가 반환된다.

- persist의 경우, save 메서드를 통해 리턴된 Entity, save 메서드에 인자로 넘겨준 Entity 객체 모두 영속성 컨텍스트가 관리한다.

- persist는 새로운 Entity를 저장하는 것이므로, 인자로 넘겨준 Entity에 대한 insert를 수행하고, 해당 객체를 영속성 컨텍스트가 관리하기 때문이다.

 

[member2, saveMember2]

- member2는 기존에 영속성 컨텍스트가 관리하던 member1.getId()를 가지는 준영속(Detach) 상태이다.

- 준영속 상태의 member2에 대해 save를 수행하면 merge가 수행되고, save가 수행된 Entity(saveMember2)가 반환된다.

- merge의 경우, save 메서드를 통해 리턴된 Entity만 영속성 컨텍스트가 관리한다.

- merge는 준영속 상태의 Entity를 DB에서 조회한다.

- DB에서 조회된 Entity를 영속성 컨텍스트에 넣고, 해당 Entity를 반환한다.

- 그러므로 반환된 Entity는 영속성 컨텍스트가 관리하지만, 기존 인자로 넘겨준 Entity는 여전히 준영속 상태이다. 

 

 

3. 정리

- JpaRepository의 save 메서드는 새로 생성되는 Entity에 대해서는 persist, 준영속 상태의 Entity에 대해서는 merge를 수행한다.

- persist, merge 둘 다 Entity를 영속상태로 만드는 동작이다.

- 차이점은 대상 Entity가 아예 새로 만들어지는 Entity인지, 기존에 영속성 컨텍스트에 의해 관리되던 Entity인지이다.

- persist의 경우, save 메서드로 넘겨준 인자와 반환값 Entity 모두 영속성 컨텍스트에 의해 관리된다.

- merge의 경우, DB에서 조회한 Entity에 대해서만 영속성 컨텍스트에서 관리하므로 반환값 Entity만 영속성 컨텍스트에 의해 관리된다.

- save 메서드를 호출했을 때 내부적으로 merge 동작이 수행된다면 영속성 컨텍스트가 관리하는 반환된 Entity를 조작해야함에 주의하자.