views:

2065

answers:

2

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?

Thanks!

+2  A: 

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.

Michael Wiles
+1  A: 

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]");
skaffman
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.
0sumgain
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.
skaffman
DAOs are injected with Session. Can you explain why that's not a good idea? Thanks.
0sumgain
Because the Session is a short-lived object, and you generally don't inject short-lived objects like that.
skaffman
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.
0sumgain