Hibernate sessionFactory.getCurrentSession () without @Transactional

In Hibernate4, Spring4 I would like to use sessionFactory.getCurrentSession()

without annotation @Transactional

. Is there a way to do this?

+3


source to share


3 answers


Simple answer: Yes , of course you can, as SessionFactory.getCurrentSession()

it is just an interface method, so you can write your own implementation class that gives you whatever Session

you like.

However, this is probably not the answer you are looking for.

We asked ourselves a similar question: why, when using Hibernate with Spring transaction management, we have to add @Transactional

to all our methods, even those that only have data SELECT

and therefore do not need to execute in the context of a database transaction?

The answer to this question is not that simple, but let's see as far as plumbing is concerned and see if we can figure it out.

First, as mentioned elsewhere on SO, the idea of ​​a Session

is fundamentally related to the idea of ​​a transaction. The interface Session

has a hint in the javadoc:

The life cycle of a session is limited to the start and end of a logical transaction. (Long transactions can span multiple database transactions.)

and a look at the javadoc of the class @Transactional

confirms that the intent is to indicate when code should be executed in the context of a transaction, which is not necessarily the context of a database transaction.

This also explains why the Spring annotation @Transactional

allows you to set a property readOnly=true

, but more on that later.

Going 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 actually defers the implementation CurrentSessionContext

, which (unless you're using JTA and you probably don't want to open that Pandora drawer) is handled by the class SpringSessionContext

:

@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:

Failed to get transaction synchronized session for current thread

when you call SessionFactory.getCurrentSession()

in a method that is not annotated with @Transactional

because it TransactionSynchronizationManager.isSynchronizationActive()

returns false

because without the annotation the @Transactional

pointcut is not executed, which would create a synchronized transaction. (see org.springframework.transaction.interceptor.TransactionInterceptor

for more information.)

So this brings us back to our use case, that is, we don't want the overhead of calling the transaction code PlatformTransactionManager

and its database when we only want to execute SELECT

against the database. An easy way to accomplish this is to just not name it SessionFactory.getCurrentSession()

and instead just openly Session

. 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 will allow you to call myHibernateService.activeUrls()

without annotation @Transactional

, but also myHibernateService.refreshUrls()

which one you want to go through PlatformTransactionManager

.

If this code looks familiar, it may be because you looked at the source OpenSessionInViewFilter

(or interceptor) that is commonly used for minification LazyLoadingException

and is also responsible for many n + 1s when programmers think they are optimizing their ORM using FetchType.LAZY

entity relationships to define but not coding your Service / Repository layer to actually get what you want to retrieve for the view you are creating.

You don't want to use the above code anyway . Instead, you probably want to use annotation @Transactional

and let the Spring and Hibernate frameworks decide which type of database transaction is really needed.

If you're worried about performance, you have several options:

No. 1. You can use Spring @Transactional(readOnly=true)

, but note that this is not necessarily a great idea. I am not a fan of using javax @Transactional

because it is more general - if you have tied your colors to a Spring mast then you can use what it has to offer. Rather, I'm wary because all it does (with current implementations) is a request that an object Connection

from the underlying database provider be marked as read-only. This can be problematic for several reasons.

First, it is possible that the database provider does not support read-only connections (e.g. jTDS JDBC driver for MSSQL server), so it might be pointless.

The second reason has to do with connection pooling. If you are using a database that supports read-only connections like PostgreSQL and connection pooling (like C3P0), you really don't want to mark some connections as read-only and then put them back into the pool, then allow them to be provisioned back in scripts where you need to write to the database. (I haven't tested this with Hibernate4 and Spring4, but this is definitely a problem with Spring3, Hibernate3 and C3P0.)

2. Use caching. With the hardware that we have access to these days, caching is probably the answer, and you have many options available to you. You can set up a second-level cache for Hibernate entities, and Spring itself has a nice spring-dump module that allows you to cache service / repository methods - take a look at how you can integrate EhCache.

3. Write that you have your own database queries using JDBC or something else. Gavin King (author of Hibernate) has said for quite some time that just because you are using Hibernate for the ORM you don't need to use it for everything: https://plus.google.com/+GavinKing/posts/LGJU1NorAvY (I can't find (See an explicit quote where he says "Don't use Hibernate for a performer SELECT

", but I think I read something a few years ago).

But there are two more important issues:

No 1. You don't have to worry about performance. And if you need to then you shouldn't read this as you should know all this already ;-) - but ignoring my wit, don't waste time optimizing atomic code, instead you should act like an engineer and look at your system as a whole (like Dirk Gently) and then decide on the most efficient way to make your system as much as possible. Remember, there are several reasons the Concorde no longer flies.

No 2 .. You don't need to use anymore SessionFactory

. JPA 2 and EntityManager

were designed to make explicit use 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: SessionFactory

both the Hibernate Criteria API and everything related to it. So I will continue to use it until they drop support from the Spring framework. Because as I said, if you've nailed your colors to the mast of the frame, then you can use whatever the infrastructure has to offer. And realistically, the main benefit of abstraction (which you can replace with underlying ORMs or database providers) is something you probably never have to worry about.

(But yeah, I was there too and did it - I had to migrate medium codebases from MSSQL to PostgreSQL, and the biggest problem is not the Spring / ORM layer, but rather database-specific code such as stored procedures and triggers. And the fact that previous devs tried to optimize queries with @Transactional(readOnly=true)

without realizing that MSSQL doesn't actually support it and it breaks when you use PostgreSQL and C3P0.m still bittersweet about it.)

+10


source


You can use Hibernate without declaring an explicit transaction boundary , but you can only invoke SELECT statements as DML statements require transactions.



0


source


Using annotations is one way to achieve declarative transaction management, but not the only one. You can also use the name space tx

, and aop

in the xml configuration. This way you have a centralized transaction configuration where you can also use wildcards to match methods. You can use sessionFactory.getCurrentSession()

in the same way. Only the style of transaction demarcation changes.

See the Spring reference documentation for details .

0


source







All Articles