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를 조작해야함에 주의하자.
'JPA' 카테고리의 다른 글
JPA Dirty Checking 시 update query 분석 (+ 모든 필드가 업데이트 되는 이유) (0) | 2023.06.29 |
---|---|
@Transactional(readOnly = true)를 왜 붙여야 하나요 (7) | 2023.05.04 |
Entity Class에 Lombok @NoArgsConstructor(access=PROTECTED)를 붙이는 이유 (0) | 2022.11.23 |
fetch join + Paging (limit) 처리 시 발생하는 문제 및 해결 (0) | 2022.07.19 |
Entity 삭제 시 JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 오류 발생 (0) | 2022.05.04 |