2

I'm in the process of upgrading an application to Hibernate 4.2 from 3.3. We're also using Spring 3.1.3 (which we can't/won't update at this time).

Some of my unit tests are now failing with

org.hibernate.HibernateException: No Session found for current thread

in the SpringSessionContext. This is not an issue with the <tx:annotation-driven /> being defined in the wrong context, or a case of missing CGLIB libraries. Most of the tests do work, which means that in most cases, the transaction proxying is working.

The cases where it is now failing seem to be around the use of NOT_SUPPORTED, NEVER, and SUPPORTED propagation types. For whatever reason the SpringSessionContext doesn't create a session in these cases.

Our use cases sometimes require that transactional boundaries don't strictly line up with method boundaries, and that sessions sometimes outlive transactions. In the Spring 3/Hibernate 3 case, the session context was bound to a thread local, and a call to SessionFactory.getCurrentSession() would return a session instance even if a transaction was not started. This is the behavior that I am looking to still have in the Hibernate 4 case.

Does anyone know a workaround for this? It's tough to align Session boundaries with a conversation instead of a transaction if Spring refuses to create a session without a valid transaction. A Session and its persistence context shouldn't be tied to an open transaction.

7
  • 1
    The propagation types mentioned will never trigger the creation of a transaction or the lookup of a session. The session is bound to the transactional boundary. There has been some discussion if this is correct or not and there has been work in that area but only for newer versions of Spring.
    – M. Deinum
    Commented Aug 29, 2014 at 14:02
  • why wouldn't propagation-required work? that would make a transaction if there isn't an existing one, or use the existing one, which is what it sounds like you want. Commented Aug 29, 2014 at 14:19
  • Propagation.REQUIRED isn't appropriate in all cases. For instance, we have a method that does multiple transactions. We set the transaction boundary on the method to be NEVER or NOT_SUPPORTED to ensure that there's no transaction started when this method is called. The method then uses the platform transaction manager to begin/commit transactions. However, even in this case the sessionFactory.getCurrentSession() is returning null and forcing the exception. That seems like a pretty major problem since HibernateTransactionManager is injected with the same session factory.
    – Jeff
    Commented Aug 29, 2014 at 14:23
  • you do realize you're setting things so you won't have a transaction, then complaining you don't have a transaction? i'd think you want requires-new for this kind of thing (you might have to break out your code into separate methods so each one gets its new transaction). Commented Aug 29, 2014 at 14:37
  • I'm not complaining that I don't have a transaction, I'm complaining that I don't have a session. Sessions should be able to exist independently of, and for longer periods of time than, transactions. The session context used in Hibernate 3 was bound to a thread local and a call to sessionFactory.getCurrentSession() worked regardless of whether there was a started transaction. That is the functionality we relied on, and what I'm looking to continue relying on.
    – Jeff
    Commented Aug 29, 2014 at 14:39

1 Answer 1

0

Worked around this issue by implementing a CurrentSessionContext that is a wrapper around a SpringSessionContext and borrowing some of the code changes that made it into Spring Framwork 4+:

public class ClassLoaderSpringSessionContext implements CurrentSessionContext {

    private final SessionFactoryImplementor sessionFactory;
    private final SpringSessionContext sessionContext;

    public ClassLoaderSpringSessionContext(final SessionFactoryImplementor sessionFactory) {
        this.sessionFactory = sessionFactory;  // This is actually some class loading logic that isn't important to this transaction problem.
        this.sessionContext = new SpringSessionContext(this.sessionFactory);
    }

    @Override
    public Session currentSession() throws HibernateException {
        try {
            return sessionContext.currentSession();
        } catch (HibernateException e) {
            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));
                TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
                sessionHolder.setSynchronizedWithTransaction(true);
                return session;
            } else {
                throw new HibernateException(
                        "Could not obtain transaction-synchronized Session for current thread");
            }
        }
    }
}

SpringSessionSynchronization was a package private class in Spring 4, so I also had to pull a version of that as a private inner class to ClassLoaderSpringSessionContext.

Hope this helps someone else.

Not the answer you're looking for? Browse other questions tagged or ask your own question.