Hibernate와 Persistence Context

김유정·2024년 3월 17일
2

Persistence Contexts

  • Persistence Context는 일종의 캐시이다. "second-level cache"와 구분하기 위해 "first-level cache"라고 부르기도 한다.
  • JPA에서 Persistence Context와 Hibernate에서 Session은 비슷한 의미를 가진다.
  • Persistence Context의 범위 내에서 데이터베이스로부터 읽은 모든 엔티티 인스턴스와 persistence Context 범위 내에서 만들어진 새로운 엔티티에 대해 인스턴스의 식별자에서 인스턴스 자체로의 교유한 매핑을 보유한다.
  • 따라서 entity 인스턴스는 주어진 persistence context에 대해서 Transient, Persistent, Detached 세 가지 상태 중 하나로 존재할 수 있다.
    • transitent: persistent context와 관계가 없는 상태
    • persistent: persistent context에 저장된 상태
    • detached: persistent context에 저장되었다가 분리된 상태

출처: https://docs.jboss.org/hibernate/orm/6.4/introduction/html_single/Hibernate_Introduction.html#persistence-contexts

Persistence Context의 생명주기

스프링 컨테이너는 트랜잭션 범위의 persistence context 전략을 기본으로 사용하기 때문에, 트랜잭션이 시작할 때 persistence context를 생성하고 트랜잭션이 끝날 때 끝낸다.

출처: https://aishwaryavaishno.wordpress.com/2013/06/07/hibernate-persistent-context-and-objects-lifecycle/comment-page-1/

따라서 여러 Entity Manager를 사용해도 한 트랜잭션으로 묶이면 영속성 컨텍스트를 공유한다.

출처: https://iyoungman.github.io/jpa/EntityManagerFactory-EntityManager-PersistenceContext/

Persistence Context의 장점

  • data aliasing 방지: 엔티티가 수정되면, 동일한 persistence context 내에서 실행되는 다른 코드에 수정 사항이 반영됨
  • 자동 dirty checking: 엔티티를 수정한 후 저장하는 작업을 수행하지 않아도 session이 flush 될 때 변경 사항이 데이터베이스와 자동으롣 동기화됨

EntityManager

  • persistence context와 상호작용할 수 있게 해주는 인터페이스이다.

출처: https://www.baeldung.com/jpa-hibernate-persistence-context

JPA Repository 메서드의 처리 과정

Persistence Context와 EntityManager를 몰라도 JPA 개발은 가능할 것이다. 그건 바로 Spring Data Jpa 덕분인데, 이건 또 뭘까?

Spring Data Jpa는 JPA를 편하게 사용할 수 있도록 한 단계 추상화시켜놓은 모듈이다. 이를 통해 EntityManager를 직접 다루지 않고도 편리하게 JPA 기술을 사용할 수 있다.

그러니까 이쯤에서 다시 정리해보자면, 우리는 Spring Data Jpa를 통해 EntityManager에 접근하고 있었고, 이 EntityManager는 Persistence Context 내에서 엔티티 인스턴스들을 관리하고 있었던 것이다.

그럼 우리가 jpa repository에서 흔하게 사용하던 save나 find와 같은 메서드는 어떤 식으로 동작할까?

출처: https://docs.jboss.org/hibernate/orm/6.4/introduction/html_single/Hibernate_Introduction.html#persistence-contexts
자세한 동작 과정을 알고 싶다면, 아래 두 구현체를 살펴볼 필요가 있다.

  • SimpleJpaRepository: Spring 프레임워크의 CrudRepository의 디폴트 구현체이다. save()나 findById() 와 같은 메서드들의 정의를 볼 수 있다.
  • SessionImpl: Session 인터페이스의 구현체인데, 이 Session은 EntityManager를 상속받는다.

SimpleJpaRepository에서 repository의 메서드에서는 결국 entitymanager의 메서드를 호출하여 요청을 처리하기 때문에, 구체적인 동작을 알고 싶다면 SessionImpl을 같이 살펴봐야한다.

처리하는 과정은 거의 비슷하기 때문에, save 메서드를 대표로 살펴보자

save()

org.springframework.data.jpa.repository.support.SimpleJpaRepository

새로운 엔티티라면, persist를 호출하고 그게 아니라면 merge 한다.

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

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

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

org.hibernate.internal.SessionImpl

SessionImpl 에서는 이벤트를 통해 요청을 처리한다.

	@Override
	public void persist(Object object) throws HibernateException {
		checkOpen();
		firePersist( new PersistEvent( null, object, this ) );
	}
    
	private void firePersist(final PersistEvent event) {
		Throwable originalException = null;
		try {
			checkTransactionSynchStatus();
			checkNoUnresolvedActionsBeforeOperation();
			// 이벤트 추가
			fastSessionServices.eventListenerGroup_PERSIST
					.fireEventOnEachListener( event, PersistEventListener::onPersist );
		}
		...
	}
	@Override @SuppressWarnings("unchecked")
	public <T> T merge(T object) throws HibernateException {
		checkOpen();
		return (T) fireMerge( new MergeEvent( null, object, this ));
	}
    
   private Object fireMerge(MergeEvent event) {
		try {
			checkTransactionSynchStatus();
			checkNoUnresolvedActionsBeforeOperation();
			// 이벤트 추가
            fastSessionServices.eventListenerGroup_MERGE
					.fireEventOnEachListener( event, MergeEventListener::onMerge );
			checkNoUnresolvedActionsAfterOperation();
		}
		...

		return event.getResult();
	}

Session 이벤트 처리

org.hibernate.event.spi.EventType

SessionImpl 에서는 이벤트를 통해 요청을 처리한다. 각 이벤트 타입에 따라 처리하기 위한 이벤트 리스너 인터페이스가 존재한다.

public final class EventType<T> {
	/**
	 * Used to assign ordinals for the standard event-types
	 */
	private static final AtomicInteger STANDARD_TYPE_COUNTER = new AtomicInteger();

	public static final EventType<LoadEventListener> LOAD = create( "load", LoadEventListener.class );
	public static final EventType<ResolveNaturalIdEventListener> RESOLVE_NATURAL_ID = create( "resolve-natural-id", ResolveNaturalIdEventListener.class );

	public static final EventType<InitializeCollectionEventListener> INIT_COLLECTION = create( "load-collection", InitializeCollectionEventListener.class );

	public static final EventType<SaveOrUpdateEventListener> SAVE_UPDATE = create( "save-update", SaveOrUpdateEventListener.class );
	public static final EventType<SaveOrUpdateEventListener> UPDATE = create( "update", SaveOrUpdateEventListener.class );
	public static final EventType<SaveOrUpdateEventListener> SAVE = create( "save", SaveOrUpdateEventListener.class );
	public static final EventType<PersistEventListener> PERSIST = create( "create", PersistEventListener.class );
	public static final EventType<PersistEventListener> PERSIST_ONFLUSH = create( "create-onflush", PersistEventListener.class );

	public static final EventType<MergeEventListener> MERGE = create( "merge", MergeEventListener.class );
	...
}

org.hibernate.event.internal.DefaultPersistEventListener

위에서 언급했던 이벤트 리스터 인터페이스는 DefaultPersistEventListener, DefaultDeleteEventListener, DefaultMergeEventListener 처럼 디폴트 구현체가 존재하며, 해당 구현체에 정의된대로 이벤트가 처리된다.

public class DefaultPersistEventListener
		extends AbstractSaveEventListener<PersistContext>
		implements PersistEventListener, CallbackRegistryConsumer {

	...
    public void onPersist(PersistEvent event) throws HibernateException {
		onPersist( event, PersistContext.create() );
	}

	public void onPersist(PersistEvent event, PersistContext createCache) throws HibernateException {
		final Object object = event.getObject();
		final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( object );
		if ( lazyInitializer != null ) {
			if ( lazyInitializer.isUninitialized() ) {
				if ( lazyInitializer.getSession() != event.getSession() ) {
					throw new PersistentObjectException( "uninitialized proxy passed to persist()" );
				}
			}
			else {
				persist( event, createCache, lazyInitializer.getImplementation() );
			}
		}
		else {
			persist( event, createCache, object );
		}
	}

	private void persist(PersistEvent event, PersistContext createCache, Object entity) {
		final EventSource source = event.getSession();
		final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity );
		final String entityName = entityName( event, entity, entityEntry );
		switch ( entityState( event, entity, entityName, entityEntry ) ) {
			case DETACHED:
				throw new PersistentObjectException( "detached entity passed to persist: "
						+ EventUtil.getLoggableName( event.getEntityName(), entity) );
			case PERSISTENT:
				entityIsPersistent( event, createCache );
				break;
			case TRANSIENT:
				entityIsTransient( event, createCache );
				break;
			case DELETED:
				entityEntry.setStatus( Status.MANAGED );
				entityEntry.setDeletedState( null );
				source.getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
				entityIsDeleted( event, createCache );
				break;
			default:
				throw new ObjectDeletedException(
						"deleted entity passed to persist",
						null,
						EventUtil.getLoggableName( event.getEntityName(), entity )
				);
		}
	}
	...
}

현재 Entity의 상태에 따라 persist에서 처리 방향이 달라지는데, 새로운 Entity를 만든 경우라면, 영속성 컨텍스트에 추가되지 않은 TRANSIENT 상태일 것이다. 그러면 entityIsTransient() 메서드를 호출하여 해당 엔티티를 영속성 컨텍스트에 추가하고 영속 상태로 처리하게 되는것이다.

정리

  • Spring Data Jpa을 통해 EntityManger를 직접 다루지 않고도 JPA 기술을 사용할 수 있다.
  • CrudRepository의 디폴트 구현체인 SimpleJpaRepository 에서는 EntityManager의 메서드를 호출하여 요청을 처리한다.
  • EntityManager의 메서드를 호출되면, 실제 구현체인 SessionImpl의 메서드가 실행된다.
  • SessionImpl에서는 이벤트를 통해 요청을 처리한다.

참고

0개의 댓글