[JPA] Hibernate 디버깅 (저장 과정)
이번 포스팅에서는 트랜잭션상에서 영속성 컨텍스트가 코드상으로 어떻게 사용되는지 확인해보려고 한다.
그전에 디버깅에 유용한 static 메서드를 소개한다.
TransactionSynchronizationManager.getResourceMap()
이 메서드는 현재 스레드에 바인딩된 트랜잭션 자원을 확인해볼 수 있다.
예를 들어 아래 코드에서 이 메서드를 실행하면 트랜잭션 실행으로 entityMangerHolder와, datasource가 바인딩될 것을 알 수 있다.
저장 과정
persistanceContext는 entity를 저장하는 환경이다.
entityManager를 사용해서 entity를 등록, 조회하는 경우 entityManger는 persistanceContext에 entity를 저장한다.
이제 아래와 같은 코드를 보자.
orderJpaRepository는 JpaRepository인터페이스를 상속받은 인터페이스이다.
save()는 entityManger.persist() or merge()를 호출하여 엔티티를 저장하고 영속화한다.
@Test
@Transactional
fun `save order`() {
val order = Order(price = 1000)
orderJpaRepository.save(order)
}
엔티티가 저장되기 전
트랜잭션 리소스에는 entityManager가 있고 persistanceContext를 참조하고 있다.
entityManager는 SessionImpl 타입으로 생성되었다. entityManager는 인터페이스로서 JPA의 스펙이라면
SessionImpl은 하이버네이트의 entityManager구현체쯤 된다. 역시 entityManager처럼 not thread-safe 하다.
그리고 persistanceContext.entityesByKey = null을 확인할 수 있는데 영속성 컨텍스트에서 관리되는 엔티티가 없다는 정도로 이해하면 된다.
엔티티 저장 과정
persist()가 호출되면 DefaultPersistEventListener에 등록된 onPersist가 호출된다.
onPersist 메서드가 호출되면 EntityEntry를 가져오는 코드가 수행된다. EntitiyEntry는 엔티티가 영속화될 때 영속성 컨텍스트에 저장된다. 현재는 영속성 컨텍스트에 값이 없으니 null을 반환한다.
다음으로 entityState를 가져온다. entity state는 PERSISTENT, TRANSIENT, DETACHED, DELETED가 있는데, 여기서는 비영속 상태이므로 TRANSIENT를 반환한다.
그다음 entityState에 따른 로직이 실행된다. 여기서는 entityIsTransient가 실행된다.
entityIsTransient는 아래 코드로 구성되어있다. createCache가 없다면 saveWithGeneratedId 메서드를 호출한다.
createCache는 merge/copy 인스턴스에 대한 엔티티 인스턴스 캐시이다.
실제 db에 insert 하기 전에 EntityEntry를 추가하는 과정이 있다. EntityEntry는 영속화하는 시점에 entity 스냅샷과 entity 참조를 가지고 있다.
AbstractSaveEventListener.performSaveOrReplicate(..)
그리고 실제 insert action이 수행된다. 이때 엔티티의 id값이 reflection으로 채워지고 영속성 컨텍스트에 저장된다.
여기서 insert가 쓰기 지연으로 동작하지 않는 이유는 id생성 전략이 auto increment여서 그렇다.
AbstractSaveEventListener.performSaveOrReplicate(..)
entitiesByKey는 영속성 컨텍스트에서 엔티티를 hashMap으로 저장한 필드이다. 여기서 key는 EntityKey클래스로 만들어지고 value는 entity 인스턴스가 된다.
EntityKey
EntityKey는 identifier, persister값이 존재하는 클래스이다.
영속성 컨텍스트에 엔티티는 @Id로 매핑된 값(pk)으로 구분된다는 말을 들은 적이 있을 거다.
EntityKey의 identifier는 그 id를 의미한다. persister는 매핑 정보 등이 담겨있는 필드이다.
정리
하이버네이트의 저장은 다음과 같은 순서로 엔티티가 저장된다.
1. persist()가 호출되면 DefaultPersistEventListener에 등록된 onPersist가 호출된다.
2. onPersist 메서드가 호출되면 EntityEntry, EntityState 등을 조회한다.
3. 엔티티 상태가 TRANSIENT(비영속)이라면 saveWithGeneratedId메서드를 호출한다.
4. 실제 db에 insert 쿼리를 날리기 전 entityEntry를 등록한다.
5. db insert쿼리가 실행된다.
6. 엔티티에 id가 매핑되고 영속성 컨텍스트에 엔티티가 등록된다.