In another part of the continuing series on getting Spring to work properly with our legacy components, I recently had to revisit our the way were were handling transactions. In the previous post on this topic, I demonstrated how you can use a HandlerInterceptor
to control your transactions. In this post, I demonstrate how you could use spring transactions, even if you can’t use the Spring way of configuring your Hibernate SessionFactory and need to support legacy code. The end result is much like putting a Ferrari body on a Pontiac Fiero, but it will accomplish the job.
The earlier solution has a significant drawback. The transaction will commit after the controller finished processing the request. Since Hibernate will not flush it’s session until a commit, all database errors will occur in a place where you can’t respond to them easily. Pushing the transaction boundary to the level below the controller (where it belongs anyway) will allow for correct error handling at the UI level. This can be accomplished with Spring Transactions.
Spring needs two things for @Transactional
annotated code to do what you expect: the session factory and the transaction manager. Our legacy code used a statically initialized class to create a session factory. My first step was to manually create the HibernateTransactionManager
with the correct session factory.
sessionHolder = new HibernateUtilSessionHolder();
SessionFactory sessionFactory = configuration.buildSessionFactory();
sessionHolder.setSessionFactory(sessionFactory);
HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory);
sessionHolder.setTxManager(htm);
The sessionHolder
is just a class I use to hold the current SessionFactory and TransactionManager. The sessionHolder
is also the interface between the legacy code TransactionManager
. Here is an example method from HibernateUtilSessionHolder
:
public Session getSession() {
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
if (sessionHolder == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("opening session");
}
Session s = sessionFactory.openSession();
sessionHolder = new SessionHolder(s);
TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder);
}
if (LOG.isDebugEnabled()) {
LOG.debug("GETTING session");
}
return sessionHolder.getSession();
}
Since I hide all the functionality of the TransactionManager behind the previous API, none of the legacy code is aware the underlying transaction model has changed and manual commits will work as they have done before.
The next trick is to get annotations working within the context of new code. Exposing the SessionFactory and TransactionManager to Spring is pretty easy. First, create two static methods to access what was created earlier:
public PlatformTransactionManager getTransactionManagerInstance() {
return sessionHolder.getTxManager();
}
public SessionFactory getSessionFactoryInstance() {
return sessionHolder.getSessionFactory();
}
Now expose them to the context by using Spring’s factory bean creation mechanism:
HIbernateUtil is the static class that created the SessionFactory and TransactionManager. The other two lines show how to use it as a factory.
Finally, all that’s left to do is to add
to the spring.xml file and mark up a bunch of code with @Transactional
. It might be a Fiero underneath, but it still feels like a Ferrari.