다중 EAR 컨텍스트에서 Spring JTA + Hibernate를 사용할 때 문제가 발생합니다.다중 EAR 컨텍스트에서 예상치 못한 수의 Hibernate 세션이 생성되었습니다.
우리는 (@Transactional을 사용하여) 여러 트랜잭션 서비스를 제공합니다. 우리의 서비스는 DAO에 대한 참조가있는 POJO입니다. 또한 여러 지속성 단위가 있습니다. 간단하게하기 위해 각 서비스/DAO가 고유 한 PU를 사용한다고 가정합니다. 서비스 2에
- 통화는 통화 당 새로운 최대 절전 모드 세션을 생성합니다 : EAR1의 서비스 1 트랜잭션을 시작하고 EAR2에서 여러 번 서비스 2를 호출하면
.
- 모든 세션은 트랜잭션이 끝날 때 (즉, Service1 호출이 완료되고 트랜잭션을 커밋 할 때) 플러시/커밋됩니다. -
중요 사항 : 봄과 최대 절전 모드는 루트 클래스 로더에 의해로드되지 않고 EAR 클래스 로더에 의해로드됩니다. 이것은 Spring과 Hibernate 클래스가 EAR 당 한 번로드된다는 것을 의미합니다.
코드 및 디버깅을 살펴보면 문제가 스레드 로컬 동기화 (즉, TransactionSynchronizationManager 클래스)에서 비롯된 것으로 생각됩니다. 어떻게됩니까 은 다음과 같습니다
- 트랜잭션은 서비스 2도 TransactionAspectSupport가 호출되는 서비스 2 방법 주위에서 개막 거래되기 때문에 는
- 서비스 1은
- EAR2 에서 서비스 2를 호출 EAR1에서 서비스 1에 의해 시작됩니다.
- 길 아래로 EntityManagerFactoryUtils는 EntityManager가 TransactionSynchronizationManager에 등록되어 있는지 확인합니다. 이것은 SharedEntityManagerCreator가 EntityManagerFactoryUtils.doGetTransactionalEntityManager()를 호출 할 때 발생합니다. 아래의 호출 스택 1을 참조하십시오.
- 등록 된 EntityManager가 없기 때문에 새로운 EntityManager 및 새로운 최대 절전 모드 세션을 생성합니다. 아래의 호출 스택 2를 참조하십시오.
- Service2 호출에서 복귀 할 때 동기화는 새로운 것으로 간주되기 때문에 제거됩니다. 이는 TransactionAspectSupport.commitTransactionAfterReturning()에서 발생합니다. 아래의 호출 스택 3을 참조하십시오.
- Service2에 대한 후속 호출은 새 엔티티 관리자 + 세션 + 동기화를 다시 만드는 과정을 반복합니다.
모든 세션도 글로벌 트랜잭션에 등록되므로 모두 해당 트랜잭션이 끝날 때 플러시/커밋됩니다. 부작용은 Service2에 대한 호출이 동일한 엔티티를 수정하면 해당 엔티티가 RDBMS에 여러 번 저장된다는 것입니다 (즉 여러 개의 동일한 업데이트 쿼리가 실행 됨).
같은 EAR 수익률에 배포 서비스 1와 서비스 2 모두 같은 시나리오 완전히 다른 결과 :
- 하나 개의 최대 절전 모드 세션은 고유 한 세션이 트랜잭션의 끝에서 플러시되는 것을
- 을 생성 (하고 단일 업데이트 쿼리가 실행 됨)
일부 시나리오에서는 잠시 동안 수백 개의 세션이 생성되어 성능 문제가 발생합니다.
Spring과 Hibernate를 루트 ClassLoader로 옮겨서 문제를 해결할 수 있지만 그것이 적절하지 않을 수도 있습니다. 두 개의 EAR이 별도의 JVM에 배포되면 어떻게됩니까? ClassLoader 계층에서 스프링을 이동하고 최대 절전 모드로 설정하면 Spring 컨텍스트 설정이 심각하게 손상됩니다.
스프링 트랜잭션의 예상되는 동작입니까? 스레드 로컬 동기화를 피하고 트랜잭션 동기화를 사용할 수 있습니까? 우리가 달성하고자하는 것에 관해서 우리의 설정 (아래 참조)이 잘못 되었습니까?
우리가 사용하는 : 봄 버전 : 3.2.8.RELEASE 최대 절전 모드 버전 : 4.1.12.Final
는 여기에 우리의 봄 설정의 몇 가지 관련 조각입니다
<tx:jta-transaction-manager />
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
abstract="true">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="${jpa.database}" />
<property name="generateDdl" value="${jpa.generateDdl}" />
<property name="showSql" value="true" />
</bean>
</property>
<property name="dataSource" ref="coreDataSource" />
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.transaction.jta.platform">${jpa.transaction.jta.platform}</prop>
<prop key="hibernate.jdbc.wrap_result_sets">${jpa.wrap_result_sets}</prop>
<prop key="hibernate.generate_statistics">${jpa.generateStatistics}</prop>
<prop key="hibernate.cache.region.factory_class">${jpa.cache.factory_class}</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.format_sql">${jpa.formatSql}</prop>
<prop key="hibernate.use_sql_comments">${jpa.useSqlComments}</prop>
</props>
</property>
</bean>
JPA를. 우리의 DAO transaction.jta.platform = org.hibernate.service.jta.platform.internal.WeblogicJtaPlatform
<bean id="referentialCoreEntityManagerFactory" parent="entityManagerFactory">
<property name="persistenceXmlLocation" value="classpath:META-INF/referential-core-persistence.xml" />
<property name="persistenceUnitName" value="referential-core" />
</bean>
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="referential-core" transaction-type="JTA" />
</persistence>
기본 클래스 기업의 관리자가 정의되는 방법을 보여주는 :
public abstract class BaseDAO<E extends IBaseEntity> extends com.sungard.decalog.framework.domain.dao.jpa.BaseDAO<E> {
private EntityManager entityManager;
/**
* @param entityClass
*/
public <U extends E> BaseDAO(Class<U> entityClass) {
super(entityClass);
}
@Override
protected final EntityManager getEntityManager() {
return entityManager;
}
@PersistenceContext(unitName = "referential-core")
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}
호출 스택 1 : 신규의 EntityManager의 제작 및 세션 :
Daemon Thread [[ACTIVE] ExecuteThread: '15' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended (breakpoint at line 239 in SessionImpl))
-> SessionImpl.<init>(Connection, SessionFactoryImpl, SessionOwner, TransactionCoordinatorImpl, boolean, long, Interceptor, boolean, boolean, ConnectionReleaseMode, String) line: 239
SessionFactoryImpl$SessionBuilderImpl.openSession() line: 1597
EntityManagerImpl.getRawSession() line: 121
EntityManagerImpl.getSession() line: 97
EntityManagerImpl(AbstractEntityManagerImpl).joinTransaction(boolean) line: 1207
EntityManagerImpl(AbstractEntityManagerImpl).postInit() line: 178
-> EntityManagerImpl.<init>(EntityManagerFactoryImpl, PersistenceContextType, PersistenceUnitTransactionType, boolean, Class, Map) line: 89
EntityManagerFactoryImpl.createEntityManager(Map) line: 179
EntityManagerFactoryImpl.createEntityManager() line: 174
GeneratedMethodAccessor450.invoke(Object, Object[]) line: not available
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
LocalContainerEntityManagerFactoryBean(AbstractEntityManagerFactoryBean).invokeProxyMethod(Method, Object[]) line: 376
AbstractEntityManagerFactoryBean$ManagedEntityManagerFactoryInvocationHandler.invoke(Object, Method, Object[]) line: 519
$Proxy116.createEntityManager() line: not available
-> EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory, Map) line: 202
SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(Object, Method, Object[]) line: 211
$Proxy118.find(Class, Object) line: not available
GenericLoaderCodifDAO(BaseDAO<E>).findById(Serializable) line: 57
GenericLoaderCodifService.findGenericLoaderCodif(String, String, String) line: 18
ReferentialFacade.findGenericLoaderCodif(String, String, String) line: 161
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
ReflectiveMethodInvocation.invokeJoinpoint() line: 183
ReflectiveMethodInvocation.proceed() line: 150
TransactionInterceptor$1.proceedWithInvocation() line: 96
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 260
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy124.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_Impl(ReferentialFacadeBean).findGenericLoaderCodif(String, String, String) line: 271
ReferentialFacadeBean_590diy_EOImpl.__WL_invoke(Object, Object[], int) line: not available
SessionRemoteMethodInvoker.invoke(BaseRemoteObject, MethodDescriptor, Object[], int, String, Class<?>) line: 40
ReferentialFacadeBean_590diy_EOImpl.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_EOImpl_WLSkel.invoke(int, Object[], Object) line: not available
ServerRequest.sendReceive() line: 174
ClusterableRemoteRef.invoke(RemoteReference, RuntimeMethodDescriptor, Object[], Method) line: 345
ClusterableRemoteRef.invoke(Remote, RuntimeMethodDescriptor, Object[], Method) line: 259
ReferentialFacadeBean_590diy_EOImpl_1036_WLStub.findGenericLoaderCodif(String, String, String) line: not available
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
RmiClientInterceptorUtils.invokeRemoteMethod(MethodInvocation, Object) line: 116
SimpleRemoteStatelessSessionProxyFactoryBean(SimpleRemoteSlsbInvokerInterceptor).doInvoke(MethodInvocation) line: 99
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractRemoteSlsbInvokerInterceptor).invokeInContext(MethodInvocation) line: 141
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractSlsbInvokerInterceptor).invoke(MethodInvocation) line: 189
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy314.findGenericLoaderCodif(String, String, String) line: not available
ReferentialService.findGenericLoaderCodif(String, String, String) line: 54
SessionService.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: 1114
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 198
$Proxy357.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: not available
SessionFacadeService.loadSession(ISessionContext, ISessionSnapshot, ISessionData, ReloadSession) line: 880
SessionFacadeService.loadSession(long, ReloadSession) line: 848
...
<snip>
호출 스택 2 : 새롭게 생성 EntityManager의 동기화 등록 :
Daemon Thread [[ACTIVE] ExecuteThread: '18' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended)
TransactionSynchronizationManager.registerSynchronization(TransactionSynchronization) line: 289
EntityManagerFactoryUtils.doGetTransactionalEntityManager(EntityManagerFactory, Map) line: 211
SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(Object, Method, Object[]) line: 211
$Proxy118.find(Class, Object) line: not available
GenericLoaderCodifDAO(BaseDAO<E>).findById(Serializable) line: 57
GenericLoaderCodifService.findGenericLoaderCodif(String, String, String) line: 18
ReferentialFacade.findGenericLoaderCodif(String, String, String) line: 161
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
ReflectiveMethodInvocation.invokeJoinpoint() line: 183
ReflectiveMethodInvocation.proceed() line: 150
TransactionInterceptor$1.proceedWithInvocation() line: 96
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 260
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy124.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_Impl(ReferentialFacadeBean).findGenericLoaderCodif(String, String, String) line: 271
ReferentialFacadeBean_590diy_EOImpl.__WL_invoke(Object, Object[], int) line: not available
SessionRemoteMethodInvoker.invoke(BaseRemoteObject, MethodDescriptor, Object[], int, String, Class<?>) line: 40
ReferentialFacadeBean_590diy_EOImpl.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_EOImpl_WLSkel.invoke(int, Object[], Object) line: not available
ServerRequest.sendReceive() line: 174
ClusterableRemoteRef.invoke(RemoteReference, RuntimeMethodDescriptor, Object[], Method) line: 345
ClusterableRemoteRef.invoke(Remote, RuntimeMethodDescriptor, Object[], Method) line: 259
ReferentialFacadeBean_590diy_EOImpl_1036_WLStub.findGenericLoaderCodif(String, String, String) line: not available
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
RmiClientInterceptorUtils.invokeRemoteMethod(MethodInvocation, Object) line: 116
SimpleRemoteStatelessSessionProxyFactoryBean(SimpleRemoteSlsbInvokerInterceptor).doInvoke(MethodInvocation) line: 99
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractRemoteSlsbInvokerInterceptor).invokeInContext(MethodInvocation) line: 141
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractSlsbInvokerInterceptor).invoke(MethodInvocation) line: 189
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy314.findGenericLoaderCodif(String, String, String) line: not available
ReferentialService.findGenericLoaderCodif(String, String, String) line: 54
SessionService.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: 1114
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 198
$Proxy357.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: not available
SessionFacadeService.loadSession(ISessionContext, ISessionSnapshot, ISessionData, ReloadSession) line: 880
SessionFacadeService.loadSession(long, ReloadSession) line: 848
...
<snip>
호출 스택 3 : EntityManager 동기화 지우기 (Service2 호출마다 새 호출이 생성됨) :
Daemon Thread [[ACTIVE] ExecuteThread: '18' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended)
TransactionSynchronizationManager.clearSynchronization() line: 328
TransactionSynchronizationManager.clear() line: 464
WebLogicJtaTransactionManager(AbstractPlatformTransactionManager).cleanupAfterCompletion(DefaultTransactionStatus) line: 1006
WebLogicJtaTransactionManager(AbstractPlatformTransactionManager).processCommit(DefaultTransactionStatus) line: 805
WebLogicJtaTransactionManager(AbstractPlatformTransactionManager).commit(TransactionStatus) line: 724
TransactionInterceptor(TransactionAspectSupport).commitTransactionAfterReturning(TransactionAspectSupport$TransactionInfo) line: 475
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 270
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy124.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_Impl(ReferentialFacadeBean).findGenericLoaderCodif(String, String, String) line: 271
ReferentialFacadeBean_590diy_EOImpl.__WL_invoke(Object, Object[], int) line: not available
SessionRemoteMethodInvoker.invoke(BaseRemoteObject, MethodDescriptor, Object[], int, String, Class<?>) line: 40
ReferentialFacadeBean_590diy_EOImpl.findGenericLoaderCodif(String, String, String) line: not available
ReferentialFacadeBean_590diy_EOImpl_WLSkel.invoke(int, Object[], Object) line: not available
ServerRequest.sendReceive() line: 174
ClusterableRemoteRef.invoke(RemoteReference, RuntimeMethodDescriptor, Object[], Method) line: 345
ClusterableRemoteRef.invoke(Remote, RuntimeMethodDescriptor, Object[], Method) line: 259
ReferentialFacadeBean_590diy_EOImpl_1036_WLStub.findGenericLoaderCodif(String, String, String) line: not available
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
RmiClientInterceptorUtils.invokeRemoteMethod(MethodInvocation, Object) line: 116
SimpleRemoteStatelessSessionProxyFactoryBean(SimpleRemoteSlsbInvokerInterceptor).doInvoke(MethodInvocation) line: 99
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractRemoteSlsbInvokerInterceptor).invokeInContext(MethodInvocation) line: 141
SimpleRemoteStatelessSessionProxyFactoryBean(AbstractSlsbInvokerInterceptor).invoke(MethodInvocation) line: 189
ReflectiveMethodInvocation.proceed() line: 172
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy314.findGenericLoaderCodif(String, String, String) line: not available
ReferentialService.findGenericLoaderCodif(String, String, String) line: 54
SessionService.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: 1114
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 198
$Proxy357.refreshSession(ISessionContext, ISessionSnapshot, ISessionData) line: not available
SessionFacadeService.loadSession(ISessionContext, ISessionSnapshot, ISessionData, ReloadSession) line: 880
SessionFacadeService.loadSession(long, ReloadSession) line: 848
...
<snip>
제 생각에 문제는 사용했던 아키텍처/디자인이 상당히 끔찍하다고 생각합니다. 당신은 2 가지 매우 나쁜 습관 (데이터베이스 공유와 개체의 소유)을 언급했습니다. 각 EAR은 도메인 (도메인 객체 및 db 스키마가있는 도메인 그룹)을 소유해야하며 다른 응용 프로그램 (또는 EAR)은 해당 데이터에 직접 액세스해서는 안되며 EAR이 소유하고 노출하는 API를 통해서만 가져와야합니다. 항아리를 움직여서 무엇인가 고치려고하지 마십시오. 먼저 아키텍처를 수정하십시오. – Augusto
제 질문은 좋은 디자인이나 나쁜 디자인 또는 하나 이상의 EAR을 갖는 것과 관련이있는 것이 아닙니다. 제가 생각하기에 봄과 최대 절전 모드에서의 문제는 여러 개의 EAR에 걸친 JTA 트랜잭션을 처리하는 것입니다. 완전히 이해할 수있는 것은 더 복잡한 사용 사례 중 하나이므로 가능하면 피해야합니다. 문제의 요점은 아키텍처를 수정하지 않고도 솔루션을 찾는 것입니다. –