|
From: Andy D. <an...@ma...> - 2005-06-20 23:26:55
|
I have some additional information on this exception. It appears that during
transaction committment things are left in an inconsistent state between the
time that AbstractPlatformTransactionManager.processCommit(...) calls
triggerBeforeCompletion(...) and the time it calls
triggerAfterCompletion(...) - though it is not
AbstractPlatformTransactionManager's fault. In my case, one of the
TransactionSynchronizations attached to the transaction is
org.springframework.orm.hibernate3.SessionFactoryUtils. This class's
implementation of beforeCompletion() invokes
TransactionSynchronizationManager.unbindResource(this.sessionFactory). If
you then attempt to suspend the transaction in a TransactionSynchronization
implementation's afterCompletion method (as I do), you will get an exception
since SessionFactoryUtils' suspend() implementation will blindly call
TransactionSynchronizationManager.unbindResource(this.sessionFactory). Of
course, since beforeCompletion() already did this, the sessionFactory is no
longer bound to the transaction, thus the exception is thrown. Following is
a log with detailed debug output (and comments):
2005-06-20 14:22:54,355 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Bound value [org.springframework.orm.hibernate3.SessionHolder@383c13] for key
[org.hibernate.impl.SessionFactoryImpl@1f37bf1] to thread [Timer-3]
2005-06-20 14:22:54,355 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Resuming JTA
transaction
2005-06-20 14:22:54,355 DEBUG [com.marathon.service.dao.jdbc.PSDAO]
-----++++]]]]>>>> TRACKING (synchronizing) with caller's transaction...
2005-06-20 14:22:54,355 DEBUG [com.marathon.service.dao.jdbc.PSDAO]
-----++++]]]]>>>> ENTERING
PSDAO.synchronizeWithTransaction(org.springframework.transaction.support.DefaultTransactionStatus@1e6356d)
... Apparently, the parent service code threw an exception at this point ...
2005-06-20 14:22:54,359 DEBUG
[org.springframework.transaction.interceptor.RuleBasedTransactionAttribute]
Applying rules to determine whether transaction should rollback on
com.marathon.AlreadyPostedException: Unable to perform operation: this entry
has already been submitted.
2005-06-20 14:22:54,359 DEBUG
[org.springframework.transaction.interceptor.RuleBasedTransactionAttribute]
Winning rollback rule is: null
2005-06-20 14:22:54,360 DEBUG
[org.springframework.transaction.interceptor.RuleBasedTransactionAttribute]
No relevant rollback rule found: applying superclass default
2005-06-20 14:22:54,360 DEBUG
[org.springframework.transaction.interceptor.TransactionInterceptor]
com.marathon.EntryManager.postApproved threw throwable
[com.marathon.AlreadyPostedException: Unable to perform operation: this entry
has already been submitted.] but this does not force transaction rollback
... Notice that the exception did not cause a rollback
2005-06-20 14:22:54,360 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
beforeCommit synchronization
2005-06-20 14:22:54,360 DEBUG
[org.springframework.orm.hibernate3.SessionFactoryUtils] Flushing Hibernate
Session on transaction synchronization
2005-06-20 14:22:54,365 DEBUG [com.marathon.service.dao.jdbc.PSDAO]
-----++++]]]]>>>> ENTERING
PSDAO.TransactionSynchronization.beforeCommit(false)
... At this point, our TransactionSynchronization starts a new transaction
using REQUIRES_NEW, causing the current transaction to suspend.
2005-06-20 14:22:54,367 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Using transaction
object [org.springframework.transaction.jta.JtaTransactionObject@bd7173]
2005-06-20 14:22:54,367 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Creating new
transaction, suspending current one
2005-06-20 14:22:54,367 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Suspending JTA
transaction
2005-06-20 14:22:54,368 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Removed value [org.springframework.orm.hibernate3.SessionHolder@383c13] for
key [org.hibernate.impl.SessionFactoryImpl@1f37bf1] from thread [Timer-3]
2005-06-20 14:22:54,368 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Clearing transaction synchronization
2005-06-20 14:22:54,368 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Beginning JTA
transaction
2005-06-20 14:22:54,368 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Initializing transaction synchronization
2005-06-20 14:22:54,368 DEBUG
[org.springframework.jdbc.datasource.DataSourceUtils] Opening JDBC Connection
2005-06-20 14:22:54,369 DEBUG
[org.springframework.jdbc.datasource.DataSourceUtils] Registering transaction
synchronization for JDBC Connection
2005-06-20 14:22:54,369 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Bound value [org.springframework.jdbc.datasource.ConnectionHolder@2b3223] for
key [org.jboss.resource.adapter.jdbc.WrapperDataSource@bed1fd] to thread
[Timer-3]
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@2b3223]
for key [org.jboss.resource.adapter.jdbc.WrapperDataSource@bed1fd] bound to
thread [Timer-3]
... Our new transaction uses straight SQL (no Hibernate) and completes
successfully...
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
beforeCommit synchronization
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
beforeCompletion synchronization
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Removed value [org.springframework.jdbc.datasource.ConnectionHolder@2b3223]
for key [org.jboss.resource.adapter.jdbc.WrapperDataSource@bed1fd] from
thread [Timer-3]
2005-06-20 14:22:54,371 DEBUG
[org.springframework.jdbc.datasource.DataSourceUtils] Closing JDBC Connection
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Initiating
transaction commit
2005-06-20 14:22:54,371 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Committing JTA
transaction
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
afterCompletion synchronization
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Clearing transaction synchronization
... Our new transaction committed without problems, so the main transaction is
now resumed...
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Resuming
suspended transaction
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Initializing transaction synchronization
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Bound value [org.springframework.orm.hibernate3.SessionHolder@383c13] for key
[org.hibernate.impl.SessionFactoryImpl@1f37bf1] to thread [Timer-3]
... Notice that the SessionFactoryImpl key that causes our woe later on is set
back up properly.
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Resuming JTA
transaction
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
beforeCompletion synchronization
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Removed value [org.springframework.orm.hibernate3.SessionHolder@383c13] for
key [org.hibernate.impl.SessionFactoryImpl@1f37bf1] from thread [Timer-3]
... At this point, it appears that Hibernate's synchronization code has
removed the infamous SessionFactoryImpl key from the
TransactionSynchronizationManager, however, as we will see later,
Hibernate3's TransactionSynchronization implementation (SessionFactoryUtils)
still holds a reference to this SessionFactoryImpl, and will try to call
TransactionSynchronizationManager.unbindResource with it once we attempt to
suspend the transaction.
2005-06-20 14:22:54,373 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Triggering
afterCompletion synchronization
2005-06-20 14:22:54,374 DEBUG
[org.springframework.orm.hibernate3.SessionFactoryUtils] Closing Hibernate
Session
2005-06-20 14:22:54,374 DEBUG [com.marathon.service.dao.jdbc.PSDAO]
-----++++]]]]>>>> ENTERING
PSDAO.TransactionSynchronization.afterCompletion(0)
... Here we have entered our custom TransactionSynchronization.afterCompletion
method, and will attempt to enter our REQUIRES_NEW TransactionTemplate
(causing the current transaction to suspend)...
2005-06-20 14:22:54,377 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Using transaction
object [org.springframework.transaction.jta.JtaTransactionObject@9b415c]
2005-06-20 14:22:54,377 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Creating new
transaction, suspending current one
2005-06-20 14:22:54,377 DEBUG
[org.springframework.transaction.jta.JtaTransactionManager] Suspending JTA
transaction
2005-06-20 14:22:54,377 DEBUG
[org.springframework.transaction.support.TransactionSynchronizationManager]
Clearing transaction synchronization
2005-06-20 14:22:54,378 WARN java.lang.IllegalStateException: No value for
key [org.hibernate.impl.SessionFactoryImpl@1f37bf1] bound to thread [Timer-3]
On Monday 20 June 2005 03:28 pm, Andy Depue wrote:
> I'm attempting to do some advanced transaction synchronization. During
> synchronization, I need to update a status row in a DB table. Of course,
> since DB access is itself transactional, I create a new transaction for the
> duration of the update using PROPAGATION_REQUIRES_NEW. The problem occurs
> when I attempt to execute this in "afterCompletion". I sometimes get this
> exception:
> java.lang.IllegalStateException: No value for key
> [org.hibernate.impl.SessionFactoryImpl@1f37bf1] bound to thread [Timer-3]
>
> Here is an abbreviated version of my code:
>
> ------------
> TransactionSynchronizationManager.registerSynchronization(new
> TransactionSynchronization() {
>
> public void suspend() { }
> public void resume() { }
> public void beforeCommit(boolean readOnly)
> {
> getTransactionTemplate().execute(new
> TransactionCallbackWithoutResult() {
> protected void doInTransactionWithoutResult(final
> TransactionStatus status) {
> update("update m_t set trans_time=null where trans_id=?",
> new Object[] { transId });
> }
> });
> }
> public void beforeCompletion() { }
> public void afterCompletion(final int status) {
> getTransactionTemplate().execute(new
> TransactionCallbackWithoutResult() {
> protected void doInTransactionWithoutResult(final
> TransactionStatus ts) {
> if(status == STATUS_ROLLED_BACK) {
> update("update m_t set s_last=trans_s_start, trans_id=null,
> trans_time=null where trans_id=?",
> new Object[] { transId} );
> } else {
> update("update m_t set trans_id=null, trans_time=null,
> trans_s_start=null where trans_id=?",
> new Object[] { transId });
> }
> }
> });
> }
> });
> ---------------
>
> The TransactionTemplate returned from getTransactionTemplate() was created
> like this:
> transactionTemplate = new TransactionTemplate(transactionManager);
>
> transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATIO
>N_REQUIRES_NEW);
> transactionTemplate.setTimeout(getTransactionTimeoutInMs());
> transactionTemplate.setReadOnly(false);
>
>
> My question is, should I be able to do this? The exception is not thrown
> in many cases - however, in this particular case, an exception was thrown
> via other code in the context of the main transaction, though it did not
> cause a rollback to occur, so the transaction was in the process of being
> committed - and while committing the main transaction, the process of
> starting a new transaction caused this related exception to be thrown.
> This is against Spring 1.2.1.
>
> - Andy
>
> PS. Here is a full stack trace, for what its worth:
> java.lang.IllegalStateException: No value for key
> [org.hibernate.impl.SessionFactoryImpl@1f37bf1] bound to thread [Timer-3]
> at
> org.springframework.transaction.support.TransactionSynchronizationManager.u
>nbindResource(TransactionSynchronizationManager.java:175) at
> org.springframework.orm.hibernate3.SessionFactoryUtils$SpringSessionSynchro
>nization.suspend(SessionFactoryUtils.java:846) at
> org.springframework.transaction.support.AbstractPlatformTransactionManager.
>suspend(AbstractPlatformTransactionManager.java:350) at
> org.springframework.transaction.support.AbstractPlatformTransactionManager.
>getExistingTransaction(AbstractPlatformTransactionManager.java:271) at
> org.springframework.transaction.support.AbstractPlatformTransactionManager.
>getTransaction(AbstractPlatformTransactionManager.java:215) at
> org.springframework.transaction.support.TransactionTemplate.execute(Transac
>tionTemplate.java:111) at
> com.marathon.service.dao.jdbc.PSDAO$2.afterCompletion(PSDAO.java:185) at
> org.springframework.transaction.support.AbstractPlatformTransactionManager.
>triggerAfterCompletion(AbstractPlatformTransactionManager.java:610) at
> org.springframework.transaction.support.AbstractPlatformTransactionManager.
>commit(AbstractPlatformTransactionManager.java:467) at
> org.springframework.transaction.interceptor.TransactionAspectSupport.doClos
>eTransactionAfterThrowing(TransactionAspectSupport.java:294) at
> org.springframework.transaction.interceptor.TransactionInterceptor.invoke(T
>ransactionInterceptor.java:61) at
> org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflec
>tiveMethodInvocation.java:144) at
> net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor
>.invoke(MethodSecurityInterceptor.java:80) at
> org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflec
>tiveMethodInvocation.java:144) at
> org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopPr
>oxy.java:174) at $Proxy58.postApproved(Unknown Source)
|