JPA 강의 듣고 공부하면서 velog 에 썻던 글이길래 티스토리에 복붙 + 그 때는 사실 그래서 이 차이가 어떤 결과를 낳는데? 라고 생각하며 사실상 글 번역 정도였었어서 몇가지를 더 추가했다.
무려 12년 전 글이다.
- JPA EntityManager: Why use persist() over merge()?
상황
JpaRepository를 extend하는 UserRepsoitory interface를 생성하였다.
- 해당 repository를 사용하여 save 하면, 기존에 존재하던 object의 경우, 데이터를 update하고, 새로운 것일 경우 insert하는 것을 확인했다.
- SimpleJpaRepository.class 라는 클래스를 살펴보면, save() method에서는 em.persist()와 em.merge()를 사용하고 있었다. 문듯, merge와 persist의 차이는 무엇인가 궁금해졌다.
그래서 이를 살펴보기 위해 단순한 번역을 하며 이해해보려 하였다. 그저 이해하는 과정의 글이다.
merge()와 persist()를 사용하는 상황
- JPA EntityManager: Why use persist() over merge()?
EntityManager.merge() 는 new object를 insert하는 것도 가능하고, 이미 존재하던 object의 update도 가능한데, 왜 persist()를 사용하는가?
- 둘 다, PersistenceContext에 entity를 add할 수 있다.
- 다만 차이점은, 이렇게 add한 entity로 그 다음에 할 수 있는 일에 관한 것이다.
merge의 리턴 인스턴스
- merge는, 영속성 컨텍스트에 의해 관리되는 instance를 리턴한다.
- PersistenceContext 안에 존재하는 것을 리턴하거나, 그 관리되는 entity의 [ 새로운 instance를 생성 ]해서 리턴해 준다.
- 즉, 어떤 경우에던, 관리되고 있는 entity의 [ 상태를 copy해서 리턴 ]해주는 것이다.
- 따라서 , em.merge(Object object) 여기에
- [ pass되는 object는 ] 영속상태가 되는 것이 아니❌고,
- [ 리턴되어오는 instance가 ] managed상태⭕이며 이를 사용할 수 있다.
===> 따라서, merge에다가 전달했던 객체를 사용하면서, "🫤 왜 쿼리가 안 날아가지???" 이런 경우가 생길 수 있다
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// 트랜잭션이 끝나면, db에, e.setSomeField(someValue)에서의 변화가 update된다.
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// 트랜잭션이 끝나도, db에는 e.setSomeField(anotherValue); 로 인한 변화가 update되지 않는다.
// merge 이후에 change를 했기 때문임
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// 트랜잭션이 끝나면, db에는 e2.setSomeField(anotherValue);로 인한 변화가 update 된다.
// 영속상태인 e2에 대한 change가 일어난 거니까.
이미 관리되는 entity에 대한 merge 호출
- 이미 영속 상태인 entity에 대해 merge를 호출하는 것은 실수하는 일임.
- 왜냐하면, managed entities는 자동으로 Hibernate에 의해 관리되고 있고, 이들의 상태는 Persistence Context를 flushing할 때, dirty checking을 통해서, DB와 동기화되고 있기 때문이다.
이를 이해해보자
- 일단, entity가 Hibernate에 의해 관리되고 있는 중이라면, 이 entity에 대한 "모든 변화"는 자동으로 DB로 propagated될 거임.
- Hibernate는 [ 현재 attached entity들을 모니터링] 하고 있다.
- 하지만, 어떤 entity가 managed되기 위해서는, 이 entity는, 올바른 entity state 에 있어야만 함.
JPA state transition을 이해하기 위한 diagram은 다음과 같다.
- 그림에서 볼 수 있듯, 하나의 entity는 네 가지 상태중 하나에 있을 수 있다.
- new(Transient)
- 새로 생성된 object는 한 번도 Hibernate session(이른바, Persistence Context)에 associated된 적이 없고, 어떤 db table의 row에 매핑된 적도 없다. --> 이런 상태를 New 상태
- Persistent
- 영속상태가 되기 위해서는, 명시적으로 EntityManager.persist 를 호출하거나,
- 현재 러닝중인 Persistence Context에 의해 관리되는 객체다.
- 이 entity에 대한 어떤 변화가 생기면, Session이 flush될 때, dirty checking등에 의해 감지되어 db로 propagated된다.
- Detached
- 현재 running중이던 Persistence Context가 closed되면, 이전에 관리되던 모든 entities들이 detached된다.
- 이후의 entity들에 대한 변화는 더이상 추적되지 않고, db에 대한 자동적인 동기화도 일어나지 않는다.
Detached 된 entity를 [ active한 Hibernate session ] 에 영속화시키기 위해서는 다음과 같이 해야 한다.
Reattaching : Hibernate는 , Session.update를 통하여 reattaching을 지원한다.
- Hibernate Session은 하나의 database row에 오직 하나의 Entity object만을 assoicate시킬 수 있다.
- 이는, Persistence context가 [ in-memory cache(first level cache)] 로 동작하며, 오직 하나의 값(entity)만이 주어진 key(그 entity type+ db ID )와 associate될 수 있기 때문이다.
- 따라서, entity가 reattach되기 위해서는, [ 해당 영속성 컨텍스트에][ 그 database row와 매핑되는 다른 JVM object ] 가 없는 경우에만 가능하다.
- Merging : deatached 된 entity의 state(source)를 [ managed entity instance] 로 copy한다. 만약 merging되는 entity의 그 equivalent가 현재 Session에 존재하지 않는다면, db로부터 fetch되어와서, 새로운 managed instance를 생성한다.
- 그리고, 그 deatached object instance는 merging이후에도 여전히, deatached상태라는 것!
- 리턴되어오는 instance가 영속상태
- Remove :
- JPA에서는, 영속상태인 entity들만이 removed가 허용되는 것을 선호하나, Hibernate에서는 deatached entity들도 delete 할 수 있다. 오직 Session.delete()를 호출함으로서.
- 엔티티가 제거된 경우, Seesion flush 시에, DELETE statment가 날아간다.
즉, merge()의 경우는, 원래의 object에 대한 duplicate를 하고, persist의 경우는 그렇지 않다.
그렇게 하지 않아도 persist 는 기존의 객체가 영속화 되는 것이고, merge 는 해당 복사본이 영속화되는 것이기 때문이다.
SimpleJpaRepository의 save
위의 글을 바탕으로 한번 , 사용하는 예를 이해해보고자 하였다.
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
- 이 코드에서도 볼 수 있듯이, em.persiste(entity)를 하는 경우에는 굳이 리턴되는 객체를 사용않고, pass한 객체를 그대로 사용한다. -> 이 entity가 영속상태가 되기 때문
- 반면, em.merge(entity) 를 하는 경우에는 em.merge(entity)로 리턴되는 인스턴스를 사용한다 -> 그래야 이것이 영속상태의 객체이므로 .
- 코드를 확인해보면, 계속해서 Entity의 타입 + Identifier 정보를 사용하여, 현재 이 Session에 존재하는 entity인지를 확인한다
- 존재하는 경우 -> merge를 사용
- 존재하지 않는 경우 -> persist를 사용하는 모습이다.
백기선님 영상 - 리턴된 객체를 사용하는 습관을 갖자
이런습관 을 들여라!!
- JPA 레포지토리에서 save 를 호출한 이후에는 리턴된 객체를 사용해주는게 좋다. ( 모두가 하이버네이트의 동작 방식을 아는 건 아니기 때문에.. 모두가 persist, merge 를 구분하며 사용하지는 않기 때문 )
- 파라미터에 넘겼던 객체를 가급적이면 사용하지 말아라
- detached 객체를 가지고 코딩했다가, 왜 쿼리가 안날아가지??😵 이러는 경우가 생길거라
'JPA' 카테고리의 다른 글
@DataJpaTest 를 사용 시 Test data 가 영속성 컨텍스트에 남아있지 않도록 만들기 (0) | 2023.05.05 |
---|