Proper way to autowire a Hibernate Session in a Spring Transaction JUnit test

23,760

Solution 1

SessionFactoryUtils.getSession() is as good as any other way of getting the Session. It does the same thing HibernateDaoSupport.getSession() would do.

The reason you need scoped-proxy is because of timing. Without the scoped-proxy it seems that it is injecting the Session before the test begins and thus before the transaction begins and so you get the errors.

By adding the scoped-proxy it proxies the Session and injects that so it does not inject the actual session upfront (before the transaction starts) but only fetches it and makes calls on it once the test is running, when it actually needs to make a call against it.

Solution 2

I think the "proper" way is the injection of the SessionFactory, and programmatically fetching the Session from it. The reason that you're getting the exception is down to the documented behaviour of SessionFactoryUtils.getSession():

Get a Hibernate Session for the given SessionFactory. Is aware of and will return any existing corresponding Session bound to the current thread, for example when using HibernateTransactionManager. Will create a new Session otherwise, if "allowCreate" is true.

Since nothing has bound a session to the current transaction, it fails.

My suggestion would be to use HibernateTemplate - define one in your context, and autowire that into your test. HibernateTemplate has most of the same operations as a war Session, but does the session handling bit for you. You should just be able to do:

hibernateTemplate.get(User.class, "[email protected]");
Share:
23,760
0sumgain
Author by

0sumgain

Updated on July 09, 2022

Comments

  • 0sumgain
    0sumgain almost 2 years

    This question is similar to a previous one. I am trying to @Autowire a Hibernate Session in one of my Spring-JUnit-Transactional tests but I am getting this exception:

    java.lang.IllegalStateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional ...

    Here is my JUnit class:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"/applicationContext.xml"})
    @TransactionConfiguration(transactionManager="transactionManager")
    @Transactional
    public class MyTest {
        @Qualifier("session")
        @Autowired
        private Session session;
    
        @Test
        public void testSomething() {
            session.get(User.class, "[email protected]");
        }
    }
    

    Every works fine if I @Autowire a SessionFactory and get my Session programmatically (instead of defining it in the Spring XML) like so:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"/applicationContext.xml"})
    @TransactionConfiguration(transactionManager="transactionManager")
    @Transactional
    public class MyTest{    
        @Qualifier("sessionFactory")
        @Autowired
        private SessionFactory sessionFactory;
    
        @Test
        public void testSomething() {
        Session session = SessionFactoryUtils.getSession(sessionFactory, false);
            session.get(User.class, "[email protected]");
        }
    }
    

    I can, however, get my original example to work if I define my Session in my Spring XML with <aop:scoped-proxy /> like so:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans  xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:tx="http://www.springframework.org/schema/tx"
            xmlns:aop="http://www.springframework.org/schema/aop"
            xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            ">
    
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
            ...
        </bean>
    
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="configLocation"><value>classpath:/hibernate.cfg.xml</value></property>
            <property name="configurationClass">
                <value>org.hibernate.cfg.AnnotationConfiguration</value>
            </property>
        </bean>
    
        <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory"/>
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
        <bean id="session" class="org.springframework.orm.hibernate3.SessionFactoryUtils" factory-method="getSession" scope="prototype">
            <constructor-arg ref="sessionFactory" />
            <constructor-arg value="false" />
            <!-- This is seems to be needed to get rid of the 'No Hibernate Session' error' -->
            <aop:scoped-proxy />
        </bean>
    </beans>
    

    My question is: Why is <aop:scoped-proxy /> needed given that there should only one thread-bounded transaction context in my unit test? What is the proper way to define my Hibernate Session bean?

  • 0sumgain
    0sumgain over 14 years
    Thanks for the response. If I set "allowCreate" to true, Spring appears to create a second non-transactional database session, i.e., the @Transactional annotation does not rollback my changes during test. The problem with autowiring a HibernateTemplate is that I have DAO-level classes that depend on Session. I guess I could have them dependent on HibernateTemplate and then do a get(User.class ...) as you suggested. However, I feel that I am violating the Law of Demeter, given that the true dependency of DAO class is Session and NOT HibernateTemplate.
  • skaffman
    skaffman over 14 years
    Are your DAOs injected with a Session, or with a SessionFactory? If you're injecting a Session, you probably want to rethink that, it's probably not a good idea.
  • 0sumgain
    0sumgain over 14 years
    DAOs are injected with Session. Can you explain why that's not a good idea? Thanks.
  • skaffman
    skaffman over 14 years
    Because the Session is a short-lived object, and you generally don't inject short-lived objects like that.
  • 0sumgain
    0sumgain over 14 years
    Thanks for the reply again. Yes that is true but I have my Session objects scoped as "request", which Spring should terminate the Session at the end of each HTTP request.