Reputation: 4698
I am using Hibernate 4.2.2.Final within a custom application server, providing a custom implementation of JTA Transaction Manager to manage transactional context. We use the DAO pattern to abstract away the details of managing hibernate sessions from the use and transparently inject transactional context when needed.
Here is how we configure the session factory:
TransactionManager transactionManager = ((BasicManagedDataSource) dataSource).getTransactionManager();
if (transactionManager != null) {
properties.put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "jta");
properties.put(AvailableSettings.JTA_PLATFORM, new MyJtaPlatform(transactionManager));
properties.put(AvailableSettings.TRANSACTION_STRATEGY, new JtaTransactionFactory());
}
Within the DAO object, when user retrieves a session within a transactional context, we register a listener to be notified of the transaction's completion so that we can flush the session:
this.session = sessionFactory.getCurrentSession();
session.setFlushMode(FlushMode.MANUAL);
if (!flushSynchronizationMap.containsKey(session)) {
Synchronization flushSynchronization = new Synchronization() {
@Override
public void beforeCompletion() {
log.beforeTxCompletes(session);
if (session.isOpen()) {
log.flushing(session);
session.flush();
session.close();
}
}
@Override
public void afterCompletion(int status) {
flushSynchronizationMap.remove(session);
log.afterTxCompletes(session);
}
};
try {
currentJtaTransaction.registerSynchronization(flushSynchronization);
flushSynchronizationMap.put(session, flushSynchronization);
} catch (Exception ex) {
throw new RuntimeException("Could not register Flush Synchronization", ex);
}
}
However the following test fails, the assert at end of test expects table to be empty, but it is not:
@Test
public void canRollbackTransaction() throws Exception {
List<SampleData> data = dao.findAll(SampleData.class);
assertThat(data).describedAs("size before insert should be 0").hasSize(0);
manager.begin();
dao.saveOrUpdate(new SampleData(12.0, "Hello World"));
dao.saveOrUpdate(new SampleData(13.0, "Hello Brave World"));
manager.rollback();
dbUnitSupport.assertDB(table("SAMPLES").columns("LABEL").dataSet());
}
I can see the transaction listener is called and session is flushed, but it looks as if the flush happened too late...
When fixing the test with an explicit flush of the current session, it passses:
@Test
public void canRollbackTransaction() throws Exception {
List<SampleData> data = dao.findAll(SampleData.class);
assertThat(data).describedAs("size before insert should be 0").hasSize(0);
manager.begin();
dao.saveOrUpdate(new SampleData(12.0, "Hello World"));
dao.saveOrUpdate(new SampleData(13.0, "Hello Brave World"));
// under the hood, flush current session
dao.flush();
manager.rollback();
dbUnitSupport.assertDB(table("SAMPLES").columns("LABEL").dataSet());
}
I traced the issue down to debug-level logs and cannot understand what's different: In the log, flush appears to be done correctly before the transaction is rollbacked.
What am I missing? I could not find examples out there which implements precisely this scenario (I may have not searched correctly...) and I think I follow what's documented in the Hibernate documentation.
I added the following to the session factory's settings:
properties.put(AvailableSettings.FLUSH_BEFORE_COMPLETION, true);
and removed custom flush done in Synchronization
. This solves the issue when doing rollback
but now, doing commit
fails. When doing an explicit getcurrentSession().flush()
both commit and rollback work fine.
Upvotes: 0
Views: 958
Reputation: 4698
The issue lied in our implementation of TransactionManager: Synchronization listeners where run within a context where current transaction had been removed, hence implicit flush()
was creating its own transaction when invoked automatically as part of transaction completion procedure. When flush was invoked explicitly things were working fine of course because transactional context was still in place.
I fixed our TM to ensure transaction context is cleared after tx is committed/rollbacked and things are now working fine.
I also removed our custom Synchronization which is a (naive) duplicate of what Hibernate provides natively and which is available when one sets the FLUSH_BEFORE_COMPLETION
flag. When in a rollback, the session is actually emptied instead of flushed meaning no interaction with the DB happens, which saves bandwidth and resource from the DBMS.
Upvotes: 1