In Hibernate4, Spring4
I would like to use sessionFactory.getCurrentSession()
without @Transactional
annotation. Is there any way to do it?
The simple answer is: Yes, of course you can as SessionFactory.getCurrentSession()
is just a method of an interface so you could write you own implementing class that gives you whatever Session
you like.
However, this is probably not the answer you are looking for.
We've been asking ourselves a similar question: why, when using Hibernate with Spring's transaction management, do we have to add @Transactional
to all our methods, even ones which only SELECT
data and thus do not need to be executed within the context of a database transaction?
The answer to this isn't so simple, but let's look at a bit of the plumbing involved and see if we can make sense of it.
Firstly, as has been mentioned elsewhere on SO, the idea of a Session
is fundamentally connected with the idea of a transaction. There's a hint in the javadoc for the Session
interface:
The lifecycle of a Session is bounded by the beginning and end of a logical transaction. (Long transactions might span several database transactions.)
and delving into the javadoc of the @Transactional
class confirms that it's purpose is to indicate when code should be executed within a "transaction context", which is not necessarily the context of a database transaction.
This also explains why Spring's @Transactional
annotation allows you to set the property readOnly=true
but more on that later.
Getting back to Spring4 and Hibernate4, when you call sessionFactory.getCurrentSession()
it actually executes the following code in SessionFactoryImpl
:
public Session getCurrentSession() throws HibernateException {
if ( currentSessionContext == null ) {
throw new HibernateException( "No CurrentSessionContext configured!" );
}
return currentSessionContext.currentSession();
}
so it's actually deferring to an implementation of CurrentSessionContext
which (unless you're using JTA and you probably don't want to open that Pandora's box) is handled by the SpringSessionContext
class:
@Override
public Session currentSession() throws HibernateException {
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
if (value instanceof Session) {
return (Session) value;
}
else if (value instanceof SessionHolder) {
SessionHolder sessionHolder = (SessionHolder) value;
Session session = sessionHolder.getSession();
if (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = session.getFlushMode();
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
return session;
}
if (this.transactionManager != null) {
try {
if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
Session session = this.jtaSessionContext.currentSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new SpringFlushSynchronization(session));
}
return session;
}
}
catch (SystemException ex) {
throw new HibernateException("JTA TransactionManager found but status check failed", ex);
}
}
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
}
else {
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
}
}
and explains why you will see the exception:
Could not obtain transaction-synchronized Session for current thread
when you call sessionFactory.getCurrentSession()
in a method which is not annotated with @Transactional
as TransactionSynchronizationManager.isSynchronizationActive()
returns false
because without the @Transactional
annotation the pointcut hasn't executed which would have created a synchronized transaction. (see org.springframework.transaction.interceptor.TransactionInterceptor
for more info.)
So this leads us back to our use-case, which is that we don't want the overhead of invoking the PlatformTransactionManager
and it's database transaction code when we only want to execute a SELECT
against the database. The easy way to achieve this is just to not call sessionFactory.getCurrentSession()
and to instead to just open a Session
explicitly. For example take this Spring managed code:
public class MyHibernateService {
@Autowired
private SessionFactory sessionFactory;
protected Session transactionalSession() {
return sessionFactory.getCurrentSession();
}
protected Session readOnlySession() {
if(TransactionSynchronizationManager.isSynchronizationActive())
return transactionalSession();
Session session = this.sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
return session;
}
public List<SalixUrl> activeUrls() {
return readOnlySession().createCriteria(SalixUrl.class)
.add(Restrictions.gt("published", LocalDateTime.now()))
.add(Restrictions.lt("removed", LocalDateTime.now()))
.list();
}
@Transactional
public List<SalixUrl> refreshUrls() {
List<SalixUrl> urls = activeUrls();
for(SalixUrl url : urls) {
url.setLastChecked(LocalDateTime.now());
transactionalSession().update(url);
}
}
}
which would allow to you call myHibernateService.activeUrls()
without having an @Transactional
annotation, but also myHibernateService.refreshUrls()
which you would want to go through the PlatformTransactionManager
.
If this code looks sightly familiar, it may be because you've looked at the source of the OpenSessionInViewFilter
(or interceptor) which is commonly used to mitigate LazyLoadingException
s and is also responsible for a lot of n+1 issues when programmers think they are optimising their ORM model by using FetchType.LAZY
to define entity relationships but haven't coded their Service/Repository layer to actually fetch what needs to be fetched for the View to be generated.
In any case, you don't want to use the above code. Instead you probably want to use the @Transactional
annotation and let the Spring and Hibernate frameworks decide what type of database transaction is actually necessary.
If you're worried about performance then you have a few options:
No 1. You can use Spring's @Transactional(readOnly=true)
but note that this isn't necessarily a great idea. I'm not advocating using the javax @Transactional
because it's more generic - if you've tied your colours to the Spring mast then you may as well use what it has to offer. Rather, I'm wary because all it does (with current implementations) is to request that the Connection
object from the underlying database provider be marked as read-only. This can be problematic for a couple of reasons.
For one, it's possible that your database provider does not support read-only connections (e.g. jTDS JDBC driver for MSSQL server) so it might be pointless.
The second reason is due to connection pooling. If you are using a database that supports read-only connections, such as PostgreSQL, and a connection pool (such as C3P0) then you don't really want to mark some connections as read-only, then return them to the pool, then allow them to be provided back in scenarios in which you need to perform database writes. (I haven't tested this with Hibernate4 and Spring4 but it was certainly a problem with Spring3, Hibernate3 and C3P0.)
2. Use caching. With the hardware we have access to these days, caching is probably the answer, and you have a lot of options available to you. You can configure a 2nd-level cache for Hibernate entities and Spring itself has a good spring-cache module that allows caching of service/repository methods - take a look at how you can integrate EhCache.
3. Write you're own database queries using JDBC or whatever. Gavin King (Hibernate author) has been making the point for quite a while that just because you use Hibernate for ORM you don't have to use it for everything: https://plus.google.com/+GavinKing/posts/LGJU1NorAvY (I can't find the explicit citation where he says "don't use Hibernate for performant SELECT
" but I think I read something a few years ago).
But there are two more important issues:
No 1. You shouldn't be worried about performance. And if you need to then you shouldn't be reading this as you should know all of it already ;-) - but ignoring my facetiousness, don't waste time with atomic code optimisation, instead you need to act like an engineer and look at your system as a whole (like Dirk Gently) and then make a judgement as to the most effective way to make your system as performant as possible. Remember: there are a few reasons why concorde doesn't fly anymore.
No 2. You probably don't need to use the SessionFactory
anymore. JPA 2 and EntityManager
have been designed to make the explicit use of SessionFactory
unnecessary. Even Emmanuel Bernard (another Hibernate author) gave us this advice a couple of years ago: http://www.theserverside.com/news/2240186700/The-JPA-20-EntityManager-vs-the-Hibernate-Session-Which-one-to-use
But you know what: I like the SessionFactory
and the Hibernate Criteria API and everything that goes with it. So I'm gonna keep using it until they deprecate support for it from the Spring framework. Because, as I've said, if you've nailed your colours to a framework mast then you may as well use all that the framework has to offer. And realistically the main benefit of abstraction (that you can swap out underlying ORM or database providers) is something you will probably never have to worry about.
(But yes, I've been there and done that too - I've had to migrate medium-sized codebases from MSSQL to PostgreSQL and the biggest problem has not been the Spring/ORM layer but has rather been the database specific code such as Stored Procedures and Triggers. And the fact that previous developers have tried to optimise queries by using @Transactional(readOnly=true)
without understanding that MSSQL doesn't actually support it and it breaks when you use PostgreSQL and C3P0. Yeah, I'm still bitter about that.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With