서론

최근 바빠서 항상 조금씩 끄적이면서 임시저장을 했는데 오랜만에 글을 마무리 해봅니다. 

이번 글에서는 EntityManger에 대한 얘기를 해보려고합니다.

 

 

목차

  • EntityManager란?
  • EntityManager가 생성되는 과정
  • EntityManager는 어떻게 thread-safe를 보장할까?

 

 

EntityManager란 무엇일까? 공식 문서에서 EntityManager는 다음과 같이 기술되어 있다. 

An EntityManager instance is associated with a persistence context.
A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.
The set of entities that can be managed by a given EntityManager instance is defined by a persistence unit. A persistence unit defines the set of all classes that are related or grouped by the application, and which must be colocated in their mapping to a single database.

 

- EntityManger는 thread-safe 해야 할까요?

멀티 스레드 환경에서 하나의 엔티티 매니저를 공유한다고 생각해보자 race condition이 발생하고 다른 스레드에서 멋대로 flush를 하거나 조회를 하는 상황이 발생할 것이다.  즉 EntityManager는 무조건 thread-safe 해야 한다.

 

- 그럼 스프링에서 우리가 주입 받는 EntityManager는 어떻게 thread-safe를 보장할까?

 


 

예제

 

우리는 스프링 프레임워크에서 다음과 같이 EntityManager를 주입받습니다.

이런식으로 직접 주입 받은적이 없다고 생각할 수도 있지만, 어차피 JpaRepository를 상속받는 인터페이스 역시 구현체인 SimpleJpaRepository에서 EntityManager를 주입 받습니다.

 

@Service
@Transactional
@RequiredArgsConstructor
public class HelloService {
    
    private final EntityManager em;

    public void save() {
        em.persist(new Hello());
    }

}

 

 

그럼 이렇게 주입 받는 EntityManager의 정체는 무엇일까?

디버깅으로 확인해보자.

주입된 엔티티 매니저의 정체다.  엔티티 매니저는 프록시인 것을 확인할 수 있으며, ShareEntityManger~ 라는 것이 보인다. EntityManager를 주입받으면 기본적으로 해당 클래스가 주입됩니다.

 

그럼 이제 해당 클래스를 따라가보자.

public abstract class SharedEntityManagerCreator {

...

	private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
    	...
    }
}

SharedEntityManagerInvocationHandler는 SharedEntityManagerCreator의 nested class로 선언되어있는 걸 확인할 수 있다.

 

그렇다면 이제 SharedEntityManagerInvocationHandler이 생성되는 과정을 트래킹 해보자.

다음 코드는 ShareEntityManagerCreator의 코드 중 일부입니다.

public abstract class SharedEntityManagerCreator {
	..
    
	public static EntityManager createSharedEntityManager(EntityManagerFactory emf) {
		return createSharedEntityManager(emf, null, true);
	}
    
	public static EntityManager createSharedEntityManager(
			EntityManagerFactory emf, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) {

		Class<?> emIfc = (emf instanceof EntityManagerFactoryInfo ?
				((EntityManagerFactoryInfo) emf).getEntityManagerInterface() : EntityManager.class);
		return createSharedEntityManager(emf, properties, synchronizedWithTransaction,
				(emIfc == null ? NO_ENTITY_MANAGER_INTERFACES : new Class<?>[] {emIfc}));
	}
    
    
	public static EntityManager createSharedEntityManager(EntityManagerFactory emf, @Nullable Map<?, ?> properties,
			boolean synchronizedWithTransaction, Class<?>... entityManagerInterfaces) {

		ClassLoader cl = null;
		if (emf instanceof EntityManagerFactoryInfo) {
			cl = ((EntityManagerFactoryInfo) emf).getBeanClassLoader();
		}
		Class<?>[] ifcs = new Class<?>[entityManagerInterfaces.length + 1];
		System.arraycopy(entityManagerInterfaces, 0, ifcs, 0, entityManagerInterfaces.length);
		ifcs[entityManagerInterfaces.length] = EntityManagerProxy.class;
		return (EntityManager) Proxy.newProxyInstance(
				(cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
				ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
	}
    
}

 

위 코드는 ShareEntityManagerCreator는 다음과 같이 createSharedEntityManager를 오버로딩 하였고, 3번의 과정을 거쳐서 최종적으로 JDK dynamic proxy로 생성한다. 확인해보자.

콜스택
최종적으로 생성하는 과정

인터페이스는 Session을 기반으로 (Session은 Hiberate 스펙이고, EntityManager JPA 표준이다. Session은 EntityManager를 상속했다),

Handler(Adivce)는 SharedEntityManagerInvocationHandler를 전달하고 최종적으로 EntityManger로 캐스팅해서 리턴한다.

 

SharedEntityManagerInvocationHandler는 다음과 같이 EntityManagerFactory를 target으로 갖고있다.

		public SharedEntityManagerInvocationHandler(
				EntityManagerFactory target, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) {

			this.targetFactory = target;
			this.properties = properties;
			this.synchronizedWithTransaction = synchronizedWithTransaction;
			initProxyClassLoader();
		}

 

그렇다. 우리가 EntityManager의 메서드를 호출하면 프록시를 이용해서 SharedEntityManagerInvocationHandler를 호출하게 되는것이다. 실제로 확인하기 위해 persist하는 과정을 따라가보면 확인할 수 있다.

	private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
	
    	...
        
		@Override
		@Nullable
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        ...
			EntityManager target = EntityManagerFactoryUtils.doGetTransactionalEntityManager(
					this.targetFactory, this.properties, this.synchronizedWithTransaction);

		..
        
 }

 

밑으로 조금 내려가다보면 다음과 같이 EntityManager를 불러오거나 생성한다. 

새로운 EntityManager(SessionImpl)이 생성되고 targer에 assign하는 것을 확인할 수 있다.

 

그리고 밑으로 더 내리면 여기서 실제 target인 EntityManager(SessionImpl)을 호출하는 것을 확인할 수 있다.

 

public class SessionImpl
		extends AbstractSessionImpl
		implements EventSource, SessionImplementor, HibernateEntityManagerImplementor {

...

	@Override
	public void persist(Object object) throws HibernateException { // 호출된다.
		checkOpen(); 
		firePersist( new PersistEvent( null, object, this ) );
	}

그렇다.

스프링에서 주입받는 EntityManager는 프록시를 주입해주고 실제로 EntityManager는 필요에 의해서 생성하는 방식으로 thread-safe를 보장한다.