简体   繁体   中英

Hibernate session flush on rollback fails with custom JTA Transaction Manager

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.

Update: 2014-05-19

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM