Closed binchoo closed 2 years ago
우리가 DAO에서 @PersistenceContext
를 붙여 EntityManager
객체를 얻을 수 있는 이유는
EntityManagerFactory
객체를 통하여 'EntityManager`가 생성되고 주입되기 때문입니다.챕터8> 예제 jpa-crud> service> SingerServiceImpl 에서 이런 동작을 확인해 볼 것입니다.
원본 코드는 EntityManager
의 필드 주입이 사용되고 있습니다.
빈 주입 추적을 용이하게 하려고, 이걸 수정자 주입으로 변경하고 브포를 걸어줍니다.
이후 SpringJPADemo어플리케이션을 디버그합니다.
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
if (this.isField) {
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
else {
if (checkPropertySkipping(pvs)) {
return;
}
try {
Method method = (Method) this.member;
ReflectionUtils.makeAccessible(method);
method.invoke(target, getResourceToInject(target, requestingBeanName)); // step into
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
getResourceToInject
호출을 따라가면 됩니다.PersistenceAnnotationBeanPostProcessor.PersistenceElement
클래스가 오버라이드 합니다.@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
// Resolves to EntityManagerFactory or EntityManager.
if (this.type != null) {
return (this.type == PersistenceContextType.EXTENDED ?
resolveExtendedEntityManager(target, requestingBeanName) :
resolveEntityManager(requestingBeanName)); // step into
}
else {
// OK, so we need an EntityManagerFactory...
return resolveEntityManagerFactory(requestingBeanName);
}
}
PersistenceElement
의 type
은 "TRANSACTION" 입니다.private EntityManager resolveEntityManager(@Nullable String requestingBeanName) {
// Obtain EntityManager reference from JNDI?
EntityManager em = getPersistenceContext(this.unitName, false);
if (em == null) {
// No pre-built EntityManager found -> build one based on factory.
// Obtain EntityManagerFactory from JNDI?
EntityManagerFactory emf = getPersistenceUnit(this.unitName);
if (emf == null) {
// Need to search for EntityManagerFactory beans.
emf = findEntityManagerFactory(this.unitName, requestingBeanName);
}
// Inject a shared transactional EntityManager proxy.
if (emf instanceof EntityManagerFactoryInfo &&
((EntityManagerFactoryInfo) emf).getEntityManagerInterface() != null) {
// Create EntityManager based on the info's vendor-specific type
// (which might be more specific than the field's type).
em = SharedEntityManagerCreator.createSharedEntityManager(
emf, this.properties, this.synchronizedWithTransaction); // step into
}
else {
// Create EntityManager based on the field's type.
em = SharedEntityManagerCreator.createSharedEntityManager(
emf, this.properties, this.synchronizedWithTransaction, getResourceType());
}
}
return em;
}
emf
빈을 획득해 옵니다.vendor-specific
하게 em
프록시 객체를 생성하도록 되어있습니다.
getEntityManagerInterface()
의 JPA 벤더 별로 제공되는 형태가 있나봅니다?SharedEntityManagerCreator.createSharedEntityManager
가 마지막 step-into가 될 것입니다.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)); // step-into
}
Prxoy.newProxyInstance()
에서 EntityManager
인터페이스를 그대로 갖는 프록시가 생성됩니다.SharedEntityManagerInvocationHandler
핸들러가 대신 처리합니다.더 깊게 파고들지 않기 위해 이상 마칩니다.
EntityManager
를 얻으려면 EntityManagerFactory
만 빈으로 구성하면 됩니다.
LocalContainerEntityMangerFactoryBean
또는 LocalEntityManagerFactoryBean
@PersistenceContext
를 필드에 붙이면 스프링의 어노테이션 후처리기가 EntityManager
프록시를 생성해 넣어줍니다.EntityManager
를 사용한 쿼리 메서드는 또 내부적으로 EntityManager
프록시를 생성합니다.
질문
JPA 사용시 EntityManager를 필드 주입받는 과정이 궁금합니다
상세 내용
챕터8의 빈 구성을 살펴보면
~EnityManagerFactoryBean
를 팩토리 빈으로 등록합니다. 이들은FactoryBean<EntityManagerFactory>
구현체이므로, 앱 컨텍스트에서EntityManagerFactory
를 획득하는 것에는 의문이 없습니다.DAO에서는
EntityManager
타입을 선언하고서@PersistenceContext
어노테이션을 붙여EntityManager
객체를 획득하여 사용합니다.의문인 점은 앱 컨텍스트에서
EntityManager
를 바로 얻는 것이 어찌 가능한가 입니다.EntityManager
는EntityManagerFactory.createEntityManager()
메서드로 얻어야 하는데 개발자는 이 메서드를 호출한 적이 없으며, 그저 어노테이션을 붙일 뿐입니다.내부에서 어떤 처리가 있을 것으로 기대됩니다. 해당 내용이 궁금하네요.
연관 챕터
61, #63
참고
https://lng1982.tistory.com/276 https://stackoverflow.com/questions/31335211/autowired-vs-persistencecontext-for-entitymanager-bean
@caffeine-library/readers-pro-spring-5