Koen Serneels opened SPR-9480 and commented

Overview

There seems yet again another issue when using the Spring 3.1 - Hibernate4(.1.3) integration.

This issue pops up so late since it only occurs in special circumstances: it has to do with the hibernate "smart" flushing which is not working.

Hibernate guarantees that every modification you make (CUD) is flushed to the database prior any other operation that can possibly be related to the outstanding queries. This is to ensure that you don't work with stale data within the same transaction.

For example:

  1. Save an entity
  2. Update the property of an entity
  3. Query the entity using the value of the property changed in the previous step in the where clause

Hibernate will make sure that the changes by step2 (and possibly also the insert of step1 - if not done already) are flushed before step3 is executed (smart flush). If this didn't happen we would never be able to retrieve the entity in step3.

The problem is that this smart flushing is not happening any more, since Hibernate does not detect that it is in a transaction.

Taken from SessionImpl L1178:

protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException {
        errorIfClosed();
        if ( ! isTransactionInProgress() ) {
            // do not auto-flush while outside a transaction
            return false;
        }
        AutoFlushEvent event = new AutoFlushEvent( querySpaces, this );
        for ( AutoFlushEventListener listener : listeners( EventType.AUTO_FLUSH ) ) {
            listener.onAutoFlush( event );
        }
        return event.isFlushRequired();
    }

What happens is before step3 is excecuted 'autoFlushIfRequired' is called (good). However isTransactionInProgress() will returns false. If you drill down in the code, you will see that it will call: transactionCoordinator.isTransactionInProgress() which will then call getTransaction().isActive(), which delegates to JtaTransaction L237:

    @Override
    public boolean isActive() throws HibernateException {
        if ( getLocalStatus() != LocalStatus.ACTIVE ) {
            return false;
        }

        final int status;
        try {
            status = userTransaction.getStatus();
        }
        catch ( SystemException se ) {
            throw new TransactionException( "Could not determine transaction status: ", se );
        }
        return JtaStatusHelper.isActive( status );
    }

The LocalStatus will be "NOT_ACTIVE" and the userTransaction is null. Why? Because no one called "begin" on the JtaTransaction.

In case of the HibernateTransactionManager it will call begin() once a transaction is started (in that case it will be JdbcTransaction rather then JtaTransaction).

So while there is a transaction started and everything is working nicely there is still a part of Hibernate which is unaware that a transaction is indeed active, which results in strange behavior like illustrated here. Note that everything else is working OK, the session will get flushed before transaction completion and everything will be in the database.

However, within the transactions we now have a stale data problem. AFAIK this is a bug in the integration, since there are no more secret properties we can use to fix this one on hibernate level.


Examples

I supplied again 2 sample applications, one with hibernate3 and the same with hibernate4 to illustrate the issue.

You can deploy the apps under context root hibernate3/hibernate4 and then point the browser to http://<host>:<port>/hibernate3/Persist or http://<host>:<port>/hibernate4/Persist.

The Servlet looks up a bean from the application context. It will then call two transactional methods on the bean.

Method 1
  • Start transaction 1
  • Save an entity of type 'TestEntity'
  • Change the property 'value' to literal 'SomeValue' on the saved entity
  • Perform a query which selects all entities of type 'TestEntity' where their 'value' property matches 'SomeValue'
  • return result
  • display result
  • End transaction 1
Method 2
  • Start transaction 2
  • Perform a query which selects all entities of type 'TestEntity' where their 'value' property matches 'SomeValue'
  • return result
  • display result
  • End transaction 2

With hibernate3 you will see this output:

Saving... Done. 
Result from read in TX: 1 Value:SomeValue 
Read from table in separate TX: 1 Value:SomeValue 

Which means that both in the same transaction and in the new transaction the data was found in database after saving/updating.

In hibernate4 however:

Saving... Done. 
Result from read in TX: 
Read from table in separate TX: 1 Value:SomeValue 

You see that in the same transaction the query did not return any results in the second output line. This is because the save of the entity and/or the update of the property where not flushed to database prior executing the query.

Note: in the output we show two properties of the 'TestEntity'.

  • The value '1' in the output is the value of the 'id' property which is the PK of the entity and auto-increment.
  • 'SomeValue' is the literal value we assigned to the 'value' property of the entity after we saved the entity.

Affects: 3.1.1

Reference URL: http://forum.springsource.org/showthread.php?126363-Session-not-flushed-with-Hibernate-4-1-3-JTA-and-Spring-transaction-management-integ&highlight=koen+serneels

Attachments: - hibernate3.zip (7.31 kB) - hibernate4.zip (7.36 kB)

Issue Links: - #14040 Session not flushed with Hibernate 4.1.3, JTA and Spring transaction management integration ("is duplicated by") - #18421 Hibernate 4 Autoflush does not work with Spring OpenSessionInViewInterceptor

Referenced from: commits https://github.com/spring-projects/spring-framework/commit/efabc6bf18f791b050ed2e082040aa4ce3bb1aa6, https://github.com/spring-projects/spring-framework/commit/dda3197c77a0b66576ebccd629c5076c0403eaf5

5 votes, 8 watchers

Comment From: spring-projects-issues

Koen Serneels commented

Note:

The sample app was tested on Glassfish. Normally it should yield the same effect on other app servers. Just make sure there is a JTA tx manager available. Also the "hibernate.transaction.jta.platform" property should be changed according to which app server you deploy it on (in case its different from Glassfish).

Comment From: spring-projects-issues

Juergen Hoeller commented

As far as I can see, Hibernate changed its configuration semantics a bit and now insists on Hibernate begin/commit calls when using JtaTransactionFactory.

Instead, for an externally managed transaction such as Spring's, you're apparently supposed to set "hibernate.transaction.factory_class" to "org.hibernate.transaction.CMTTransactionFactory", in which case the transaction activity check will delegate to the JTA subsystem only and not insist on a local active flag within Hibernate.

Can you confirm that you were using JtaTransactionFactory in the setup above? Does the behavior change when you switch to CMTTransactionFactory?

Juergen

Comment From: spring-projects-issues

Koen Serneels commented

Thanks for having a look at this Juergen. Your suggestion seems to work for the supplied sample application at least. If I change hibernate.transaction.factory_class to org.hibernate.transaction.CMTTransactionFactory, the stale read no longer occurs and the application works like its hibernate3 variant. I never looked at CMTTransactionFactory since it makes me thing about JEE environments. However, the JavaDoc on CMTTransaction explicitly states the name is misleading :-)

**
 * Implements a transaction strategy for Container Managed Transaction (CMT) scenarios.  All work is done in
 * the context of the container managed transaction.
 * <p/>
 * The term 'CMT' is potentially misleading; the pertinent point simply being that the transactions are being
 * managed by something other than the Hibernate transaction mechanism.
 * <p/>
 * Additionally, this strategy does *not* attempt to access or use the {@link javax.transaction.UserTransaction} since
 * in the actual case CMT access to the {@link javax.transaction.UserTransaction} is explicitly disallowed.  Instead
 * we use the JTA {@link javax.transaction.Transaction} object obtained from the {@link TransactionManager}
 *
 * @author Gavin King
 * @author Steve Ebersole
 */

So this seems to be the correct direction at least. I'm going to change this in our code base and perform some checks to make sure everything is solved with this configuration. I'll post back here with my findings.

BTW; it would be a good idea to spend some lines of explanation in the Spring reference guide about this. At this moment there is no central place which gives a clear view on how to configure Spring with hibernate 4.x And then I'm talking about these hibernate specific configuration options like "hibernate.transaction.jta.platform" and "hibernate.transaction.factory_class" which were not required with Spring and hibernate3. (at least they did not needed to be defined explicitly in the applications configuration)

Comment From: spring-projects-issues

Juergen Hoeller commented

On a related note, your configuration seems to use an @Bean method with a LocalSessionFactoryBean. In Spring's Hibernate 4 support, LocalSessionFactoryBean is just a thin wrapper on top of our new LocalSessionFactoryBuilder class, which is exactly meant for use in @Bean methods since it has a more natural programmatic factory contract (instead of Spring's FactoryBean contract). So your @Bean method should look more natural when converted to use LocalSessionFactoryBuilder, without losing any configuration power.

Juergen

Comment From: spring-projects-issues

Juergen Hoeller commented

Aside from documenting this in LocalSessionFactoryBuilder/Bean's javadoc, I'm also adding a setJtaTransactionManager method similar to what we have in the Hibernate3 variant. With this method, you would typically specify your Spring JtaTransactionManager instance, which automatically extracts the correct JTA TransactionManager reference and sets Hibernate's properties accordingly. Alternatively, you may of course set those Hibernate properties directly.

Note that you should specify the JTA TransactionManager with Hibernate3 as well, for cache synchronization to work in all cases. You may do so through the old "hibernate.transaction.manager_lookup_class" property or through LocalSessionFactoryBean's setJtaTransactionManager method.

Juergen

Comment From: spring-projects-issues

Koen Serneels commented

Thanks for the update on LocalSessionFactoryBuilder and the note on hibernate.transaction.manager_lookup_class. But I still have some questions about this:

It is true that the transaction manager could be set explicitly on Hibernate3 LocalSessionFactoryBean, and no longer in the Hibernate4 LocalSessionFactoryBean. In the Hibernate3 LocalSessionFactoryBean, Spring would configure this if the jtaTransactionManager was explicitly set:

if (this.jtaTransactionManager != null) {
     // Set Spring-provided JTA TransactionManager as Hibernate property.
     config.setProperty(
     Environment.TRANSACTION_STRATEGY, JTATransactionFactory.class.getName());
     config.setProperty(
     Environment.TRANSACTION_MANAGER_STRATEGY, LocalTransactionManagerLookup.class.getName());
}

However, I never configured either hibernate.transaction.factory_class (Environment.TRANSACTION_STRATEGY) or hibernate.transaction.manager_lookup_class (TRANSACTION_MANAGER_STRATEGY). Neither did I ever use the setJtaTransactionManager method to set the transaction manager. With hibernate3 everything always worked in a JTA environment, we never experienced any problems. So if I understand you correctly these settings were also required in Hibernate3? Either by specifying them via the hibernate config as properties or by setting the TransactionManager explicitly on the LocalSessionFactoryBean using the setJtaTransactionManager? Could you share some scenario's in which not doing this would yield problems?

Also, as we can see in the code snippet, hibernate3 LocalSessionFactoryBean configures the JTATransactionFactory in case a TransactionManager is set. Is this actually correct? Since we just learned that in case of a mechanism managing the transaction outside of the Hibernate transaction API one should use CMTTransactionFactory.

From JTATransaction:

/**
 * {@link Transaction} implementation based on transaction management through
 * a JTA {@link UserTransaction}.  Similar to {@link CMTTransaction}, except
 * here we are actually managing the transactions through the Hibernate
 * transaction mechanism.
 *
 * @author Gavin King
 * @author Steve Ebersole
 * @author Les Hazlewood
 */

So I don't understand why hibernate3 LocalSessionFactoryBean sets JTATransactionFactory, while with Hibernate4 CMTTransactionFactory should be set (all in the same JTA/Spring tx managed cases). Furhtermore, which factory are you going to specify when you add support for the jtaTransactionFactory to the hibernate4 LocalSessionFactoryBean; JTA or CMT?

Thanks.

Comment From: spring-projects-issues

Juergen Hoeller commented

With Hibernate3, JTATransactionFactory was actually usable for Spring-driven transactions since it did not have any specific checks for local transaction handling in there. It was basically a superset of CMTTransactionFactory, allowing both for externally managed JTA transactions and locally managed Hibernate transactions. So technically we could/should have used CMTTransactionFactory for Hibernate3 as well, but there was no strong technical need to.

With Hibernate4, Hibernate internally changed the semantics of JTATransactionFactory: They are enforcing the use of the Hibernate transaction API now, otherwise they don't consider the transaction to be active (through checking that local activity flag). This is why we need to use CMTTransactionFactory now, and this is also what we're setting in the new Hibernate4 setJtaTransactionManager method and what we're documenting in the corresponding javadoc.

As for not configuring Hibernate3 for JTA, good point, this does actually work quite well without special setup as long as you're consistently using Spring-driven transactions and as long as you're not using a second-level cache provider that would need direct XA integration through Hibernate. For Hibernate3, Spring did quite a bit of special Hibernate handling to make that work internally, which unfortunately isn't quite possible with Hibernate4 anymore.

So from that perspective, explicit JTA setup is recommended for Hibernate3 as well - in particular recommended by the Hibernate team itself - but not strictly necessary when using Spring's Hibernate3 integration. With Hibernate4, Spring is following the Hibernate team's recommendations more closely and hence relies on explicit JTA setup. What we can do for 3.1.2 now is simply to make that explicit JTA setup better documented and more obvious in the API.

Juergen

Comment From: spring-projects-issues

Koen Serneels commented

Ok, thanks for the clear explanation!

Comment From: richakhurana19

what are we supposed to do to be able to fix it? I have legacy code base with hibernate4.x, spring4.x, bitronix jta and jboss seam 2.3.1. I need for them to participate in the same transaction initiated by Spring. However LocalStatus is not marked active by bitronix transaction manager. Hence envers is failing.