스프링 컨테이너는 트랜잭션 범위의 persistence context 전략을 기본으로 사용하기 때문에, 트랜잭션이 시작할 때 persistence context를 생성하고 트랜잭션이 끝날 때 끝낸다.
따라서 여러 Entity Manager를 사용해도 한 트랜잭션으로 묶이면 영속성 컨텍스트를 공유한다.
출처: https://iyoungman.github.io/jpa/EntityManagerFactory-EntityManager-PersistenceContext/
출처: https://www.baeldung.com/jpa-hibernate-persistence-context
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에서 repository의 메서드에서는 결국 entitymanager의 메서드를 호출하여 요청을 처리하기 때문에, 구체적인 동작을 알고 싶다면 SessionImpl을 같이 살펴봐야한다.
처리하는 과정은 거의 비슷하기 때문에, save 메서드를 대표로 살펴보자
새로운 엔티티라면, 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);
}
}
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();
}
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 );
...
}
위에서 언급했던 이벤트 리스터 인터페이스는 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()
메서드를 호출하여 해당 엔티티를 영속성 컨텍스트에 추가하고 영속 상태로 처리하게 되는것이다.