So here's a common scenario.
You've got a mature Java app that uses:
- A JTA implementation (probably provided by your container) for distributed transactions
JtaTransactionManager abstraction for app-defined transaction management
- Hibernate 3 for ORM
LocalSessionFactoryBean for building a Hibernate 3
Now, for one reason or another, you want (or need) to upgrade to Hibernate 4. Maybe you are finally moving off of that old application server (which is why your app was stuck using Hibernate 3 in the first place), and the new one requires you to migrate to Hibernate 4. Maybe you were finally able to convince the powers-that-be that you need to spend some time paying down technical debt.
Regardless, your task is to get this established application running with Hibernate 4 ASAP. So you update your build dependencies, change to Spring's Hibernate 4 version of
LocalSessionFactoryBean, refactor a few things, make sure your data-access code is using
SessionFactory.getCurrentSession(), and voilà. Everything is up and running again.
Then you encounter a little problem.
Suddenly your database operations start failing with
org.hibernate.HibernateException: Unable to locate current JTA transaction. "Something must be configured incorrectly," you think to yourself. You go back and double-check. You re-read migration guides and reference documents. You triple-check. You try several permutations of changing Hibernate configuration and Spring configuration - to no avail. You step through code, where it appears that everything that should be firing is firing. "This was working fine with Hibernate 3. What is going on?"
There's one critical thing you haven't discovered yet. Spring quietly dropped their support for the
Propagation.SUPPORTS transaction definition, at least with Hibernate 4 and JTA.
You finally find SPR-9020. Then you realize it was your read-only operations that were failing - the ones that are configured to support execution within a transaction, if it's available, but do not require a transaction for successful execution. This seems fairly standard practice.
Apparently Spring was doing some fairly complicated hackery in their Hibernate 3 integration all along, and they chose to abandon it in their Hibernate 4 support. Also, the Hibernate team has been lobbying for Spring to do things the "Hibernate way", part of which means every Hibernate operation should be executed within a transaction, read-only or not.
This is all well-and-good, but if Spring drops support for a widely used feature such as
Propagation.SUPPORTS transaction demarcation, then they should at least mention this in their documentation, if not deprecate the feature entirely. As of yet, neither has been done.
So how do you get around this problem?
I know of at least two options:
- Per Juergen Hoeller, replace
- Define your own implementation of Hibernate's
CurrentSessionContext that can open a Hibernate
Session (when not already available) and explicitly register it with Spring's
Number 1 comes with the higher recommendation and is probably your best bet for future-proofing your application.
Number 2 can be used as an alternative if you really can't (or just don't want to) require transactions for read-only operations, such as if you are maintaining parallel builds and runtimes for the same codebase. In this case, check out my implementation in this Gist, which is a modification of Matías Mirabelli's original.
I assume that, if you're still reading this, you share at least a little of my frustration. I have to say I'm quite disappointed with the Spring team on this. They usually document their software changes fairly well, but they totally dropped the ball on this one. Hopefully you found this before you wasted too much time on your problem. Leave me a comment if this helped you or if you have similar problems that this doesn't fix.
P.S. The wording used in the title was influenced by Iris Shoor's findings. Apparently negative and violent terms draw more interest than their opposites.