Rejected
Details
Assignee
UnassignedUnassignedReporter
Andrew FleggAndrew FleggComponents
Affects versions
Priority
Major
Details
Details
Assignee
Unassigned
UnassignedReporter
Andrew Flegg
Andrew FleggComponents
Affects versions
Priority
Created September 9, 2011 at 4:32 PM
Updated July 8, 2014 at 3:10 PM
Resolved July 8, 2014 at 3:10 PM
1. Background
In a JTA environment (using an XA data source),
JDBCContext
registersCacheSynchronization
objects for running before & after transaction completion.If a stateless session is opened within an existing session and using the same connection, a
CacheSynchronization
will be registered against theStatelessSessionImpl
. However,org.hibernate.transaction.CacheSynchronization.beforeCompletion()
is called when the transaction ends; not when the stateless session ends.In pseudo-code this might look like, using Spring's
@Transactional
aspect:@Transactional public void test() { Session session = sessionFactory.getCurrentSession(); session.createQuery("from People").list(); StatelessSession statelessSession = sessionFactory.openStatelessSession(session.connection()); statelessSession.createQuery("from Dogs").list(); statelessSession.close(); session.createQuery("from People").list(); }
org.hibernate.impl.StatelessSessionImpl.managedFlush()
will error if the session has been closed.CacheSynchronization.beforeCompletion()
will callmanagedFlush()
if:StatelessSessionImpl.isFlushModeNever()
is false.and
StatelessSessionImpl.isFlushBeforeCompletionEnabled()
is trueand the transaction isn't rollback-only.
As expected,
StatelessSessionImpl
has hardcoded false and true forisFlushModeNever()
andisFlushBeforeCompletionEnabled()
, respectively.The above pseudo-code will therefore fail when the transaction is committed. The top of the stack-trace is:
Exception caught from before_completion synchronization operation: org.hibernate.SessionException: Session is closed! at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:72) at org.hibernate.impl.StatelessSessionImpl.managedFlush(StatelessSessionImpl.java:332) at org.hibernate.transaction.CacheSynchronization.beforeCompletion(CacheSynchronization.java:88) at com.ibm.tx.jta.RegisteredSyncs.coreDistributeBefore(RegisteredSyncs.java:289) at com.ibm.ws.tx.jta.RegisteredSyncs.distributeBefore(RegisteredSyncs.java:150) at com.ibm.ws.tx.jta.TransactionImpl.prePrepare(TransactionImpl.java:2316) at com.ibm.ws.tx.jta.TransactionImpl.stage1CommitProcessing(TransactionImpl.java:536) at com.ibm.tx.jta.TransactionImpl.processCommit(TransactionImpl.java:983) at com.ibm.tx.jta.TransactionImpl.commit(TransactionImpl.java:918)
Looking at github, this still affects
CacheSynchronization
in Hibernate 3.6.2. Proposed fix
The proposed fix is to make
CacheSynchronization
checkTransactionFactory.Context#isClosed()
.From:
flush = !ctx.isFlushModeNever() && ctx.isFlushBeforeCompletionEnabled() && !JTAHelper.isRollback( transaction.getStatus() ); //actually, this last test is probably unnecessary, since //beforeCompletion() doesn't get called during rollback
To:
flush = !ctx.isFlushModeNever() && ctx.isFlushBeforeCompletionEnabled() && !ctx.isClosed() && !JTAHelper.isRollback( transaction.getStatus() ); //actually, this last test is probably unnecessary, since //beforeCompletion() doesn't get called during rollback
2.1. Mockito-based test
/** * Check the behaviour of {@link CacheSynchronization} when the session has * been closed. * * @throws SystemException if something goes wrong */ public void testCacheSynchronisation() throws SystemException { final TransactionFactory.Context ctx = mock(TransactionFactory.Context.class); doThrow(new SessionException("Session is closed!")).when(ctx).managedFlush(); when(ctx.isClosed()).thenReturn(true); when(ctx.isFlushModeNever()).thenReturn(false); when(ctx.isFlushBeforeCompletionEnabled()).thenReturn(true); final javax.transaction.Transaction transaction = mock(javax.transaction.Transaction.class); when(transaction.getStatus()).thenReturn(0); final org.hibernate.Transaction tx = mock(org.hibernate.Transaction.class); final JDBCContext jdbcContext = mock(JDBCContext.class); new CacheSynchronization(ctx, jdbcContext, transaction, tx).beforeCompletion(); verify(jdbcContext).beforeTransactionCompletion(tx); }
2.2. Alternatives
StatelessSessionImpl.isFlushBeforeCompletionEnabled()
could return a boolean based on whether or not it was closed, rather thanCacheSynchronization
checking for closedness itself.