This week it was time to upgrade our code base to the latest Hibernate 4.x. We postponed our migration (still being on Hibernate 3.3) since the newer maintenance releases of the 3.x branch required some API changes which were apparently still in flux. An example is the UserType API which was still showing flaws and was going to be finalized in Hibernate 4. The migration went quite smooth. Adapting the UserType’s to the new interface was pretty straightforward. There were some hick ups here and there but nothing painful.
The thing to watch out for is the Spring integration. If you have been using Spring with Hibernate before, you will be using the LocalSessionFactoryBean (or AnnotationSessionFactoryBean) for creating the SessionFactory. For hibernate 4
there is a separate one in its own package: org.springframework.orm. hibernate4 instead of org.springframework.orm. hibernate3. The LocalSessionFactoryBean from the hibernate 4 package will do for both mapping files as well as annotated entities, so you only need one for both flavors.
When the upgrade was done, all our tests were running and the applications were also running fine on Tomcat using the local Hibernate transaction manager. However when running on Glassfish using JTA transactions (and Spring’s JtaTransactionManager) we got the ‘No Session found for current thread’ when calling sessionFactory.getCurrentSession();
So it seemed I missed something in relation with the JTA configuration. As you normally do with the Spring-Hibernate integration, you let Spring drive the transactions. You specify a transaction manager and Spring makes sure all resources are registered with the transaction manager and eventually call commit or rollback. Spring will integrate with Hibernate, so it makes sure the session is flushed before a transaction commit.
When using hibernate 3 and the hibernate 3 Spring integration, the session is bound to a thread local. This technique allow you to use the sessionFactory.getCurrentSession() to obtain a open session anywhere inside the active transaction. This is both the case for the local HibernateTransactionManager as for the JtaTransactionManager. However, as of the hibernate 4 integration, the hibernate session will be bound to the currently running JTA transaction instead.
From a user point of view nothing changes as sessionFactory.getCurrentSession() will still do its job. But when running JTA this means that Hibernate must be able to lookup the transaction manager to able to register the session with the currently running transaction. This is new if you are coming from Hibernate 3 with Spring, in fact, you did not have to configure anything in regards to transactions in your Hibernate SessionFactory (or LocalSessionFactoryBean) configuration. As it turned out, with the Hibernate 4 Spring integration the transaction manager lookup configuration is effectively done by hibernate and not by Spring’s LocalSessionFactoryBean. The solution was pretty simple; adding this to the Hibernate (LocalSessionFactoryBean) configuration solved our problems:
<prop key="hibernate.transaction.jta.platform">org.hibernate.service.jta.platform.internal.SunOneJtaPlatform</prop>
‘SunOneJtaPlatform’ should then be replaced by a subclass that reflects your container.
See the API docs for the available subclasses. What this class does is actually telling Hibernate how it can lookup the transaction manager for your environment. If you don’t configure this there will be nothing for Hibernate to bind the session to hence throwing the exception. There is also a property:
hibernate.current_session_context_class
Which should point to org.springframework.orm.hibernate4.SpringSessionContext, but this automatically done by the LocalSessionFactoryBean, so there is no need to specify it in the configuration.
As this solved my ‘No Session found for current thread’ problem, there was still another one. Changes made to the database inside a transaction were not visible after a successful transaction commit. After some research I found out that no one was calling session.flush(). While with the hibernate 3 integration there was a SpringSessionSynchronization registered which would call session.flush() prior transaction commit (in the beforeCommmit method).
In the hibernate 4 integration there is a SpringFlushSynchronization registered, which, as its name says, will perform a flush also. However, this is only implemented in the actual “flush” method of the TransactionSynchronization, and this method gets never called.
I raised an issue for this on Spring bugtracker, including two sample application which illustrates the problem clearly. The first uses Hibernate 3 and the other is the exact same application but this time using hibernte 4. The second will show that no information is actually persisted to database (both apps are tested under the latest Glassfish 3.1.2) Until now the best workaround seems to be creating a flushing Aspect that wraps around @Transactional annotations. Using the order attribute you can order the transactional annotation to be applied before your flushing Aspect. This way your Aspect is still running inside the transaction, and is able to flush the session. It can obtain the session the normal way by injecting the SessionFactory (one way or the other) and then calling sessionFactory.getCurrentSession().flush().
<tx:annotation-driven order="1"> <bean id="flushinAspect" clas="..."> <property name="order" value="2"></property></bean></tx:annotation-driven>
or, if using the annotation configuration:
@EnableTransactionManagement(order=1)
Update:
There was some feedback on the issue. As it turns out it does not seem to be a bug in the Spring Hibernate integration, but a missing Hibernate configuration element. Apparently the ‘hibernate.transaction.factory_class’ needs to be set to JTA, the default is JDBC which depends on the Hibernate Transaction API for explicit transaction management. By setting this to JTA the necessary synchronizations are registered by hibernate which will perform the flush. See the Spring
https://jira.springsource.org/browse/SPR-9404
Update 2:
As it turns out, after correcting the configuration as proposed on the preceding issue, there was still a problem. I’m not going to repeat everything, you can find detailed information in the second bug entry I submitted here: https://jira.springsource.org/browse/SPR-9480 It basically comes down to the fact that in a JTA scenario with the JtaTransactionFactory configured, hibernate does not detect that it is in a transaction and will therefore not execute intermediate flushes. With the JtaTransactionFactory configured, you are expected to control the transaction via the Hibernate API rather then via an external (Spring in our case) mechanism. One of the side effects is that you might be reading stale data in some cases.
Example:
//[START TX1]Query query = session.createQuery('from Person p where p.firstName = :firstName and p.lastName = :lastName');Person johnDoe = (Person)query.setString('firstName','john').setString('lastName','doe').uniqueResult();johnDoe.setFirstName('Jim');Person jimDoe = (Person)query.setString('firstName','jim').setString('lastName','doe').uniqueResult();//[END TX1]
What happens is that when performing the second query at line 5, hibernate should detect that it should flush the previous update which was made to the attached entity on line 4 (updating the name from ‘john’ to ‘jim’). However, because hibernate is not aware it is running inside an active transaction, the intermediate flushing doesn’t work. It will only flush once before the transaction commits. This results in stale data, as the 2nd query would not find ‘jim’ and return null instead. The solution (see the reply from Juergen Hoeller in the issue) is configuring hibernate.transaction.factory_class to org.hibernate.transaction.CMTTransactionFactory instead. At first I was a bit sceptical, as CMT makes be thing about EJB containers. However, if you read the Java doc on CMTTransaction it does make sense:
/** * Implements a transaction strategy for Container Managed Transaction (CMT) scenarios. All work is done in * the context of the container managed transaction. * * 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. * * 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}
After that everything seems to work fine. So to conclude, if you want hibernate to manage the JTA transaction via the UserTransaction, you should use JtaTransactionFactory. In that case you must use the Hibernate API to control the transaction. If there is someone else managing the transaction (Spring, EJB container …) you should use CMTTransactionFactory instead. Hibernate will then revert to registering synchronisations, by checking for active javax.transaction.Transaction using the javax.transaction.TransactionManager. If there is any other issue popping up I’ll update this entry accordingly.
Reference: Migrating from Hibernate 3 to 4 with Spring integration from our JCG partner Koen Serneels at the Koen Serneels – Technology blog blog.
Source : feedproxy[dot]google[dot]com
No comments:
Post a Comment