views:

405

answers:

3

Hi, I've got a web app with Spring set up to create my hibernate session factory (singleton) and session and transaction (both are request scoped), but it is destroying the session and transaction in the wrong order. How can i configure it so that the transaction is destroyed before the session? Here's my spring applicationContext.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
      "http://www.springframework.org/dtd/spring-beans-2.0.dtd"&gt;
<beans>
  <bean id="hibernateSessionFactory" scope="singleton"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />
  </bean>

  <!-- The per-http request hibernate session -->
  <bean id="hibernateSession" factory-bean="hibernateSessionFactory"
    factory-method="openSession" destroy-method="close" scope="request" />

  <!--  The per-http request transaction (i need this to be destroyed BEFORE the session) -->
  <bean id="hibernateTransaction" factory-bean="hibernateSession"
    factory-method="beginTransaction" destroy-method="commit" scope="request" />
</beans>

And here's the log that shows it closing the session before it closes the transaction:

16111 [http-8080-3] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter  - Invoking destroy method 'close' on bean with name 'hibernateSession'
16111 [http-8080-3] DEBUG org.hibernate.jdbc.ConnectionManager  - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
16111 [http-8080-3] DEBUG com.mchange.v2.resourcepool.BasicResourcePool  - trace com.mchange.v2.resourcepool.BasicResourcePool@17e4dee [managed: 4, unused: 3, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@19a8416)
16111 [http-8080-3] DEBUG org.springframework.beans.factory.support.DisposableBeanAdapter  - Invoking destroy method 'commit' on bean with name 'hibernateTransaction'
16111 [http-8080-3] DEBUG org.hibernate.transaction.JDBCTransaction  - commit
16111 [http-8080-3] WARN  org.springframework.beans.factory.support.DisposableBeanAdapter  - Invocation of destroy method 'commit' failed on bean with name 'hibernateTransaction'
org.hibernate.SessionException: Session is closed
+2  A: 

You could declare that hibernateTransaction depends-on hibernateSession. Since the container will instantiate beans in dependency order (barring cyclic dependencies), and tear them down in reverse dependency order, this should do the trick.

meriton
It doesn't work for request-scoped beans. According to the documentation, depends-on specifies destruction order only for singleton-scoped beans.
axtavt
Yeah, i already tried depends-on, it didn't help. However, thanks for the answer!
Chris
+1  A: 

It seems to be that the order of destory method calls for non-singleton-scoped beans is completely out of control. From docs (3.4.3 Using depends-on):

The depends-on attribute in the bean definition can specify both an initialization time dependency and, in the case of singleton beans only, a corresponding destroy time dependency

You may create a helper object and delegate creation and descruction of your beans to it:

public class HelperObject
{
    private SessionFactory factory;
    private Session session;
    private Transaction tx;

    public void init()
    {
        session = factory.createSession();
        tx = session.beginTransaction();
    }

    public void destroy()
    {
        tx.commit();
        session.close();
    }

    ...
} 

--

<bean id = "helperObject" class = "HelperObject" scope = "request" init-method = "init" destroy-method = "destroy">
    <property name = "factory" ref = "hibernateSessionFactory" />
</bean>

<bean id="hibernateSession" factory-bean="helperObject" 
    factory-method="getSession" scope="request" /> 

<bean id="hibernateTransaction" factory-bean="helperObject" 
    factory-method="getTransaction" scope="request" />

And, after all, perhaps it is not the best way to manage Hibernate sessions and transactions in Spring. Consider using of Spring's built-in Hibernate and transactions support.

EDIT: Well, the right way to manage transactions is:

  • You don't need request-scoped session and transaction beans
  • You shouldn't call createSession on the session factory returned by org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean. You can inject this session factory into your beans and call getCurrentSession when you need a session, a it will work fine.
  • You can use declarative transaction management (@Transactional annotations on the transactional methods). To make it work you should add to your config:

.

<bean id="transactionManager"
    class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="hibernateSessionFactory"/>
</bean>

<tx:annotation-driven/>
  • For more information, see the links above
axtavt
Hi,I had a suspicion that spring had built-in options for managing the session/txn, however after reading those two links i'm still no closer to understanding how they'd work. I think i'll go for your 'helper class' option, that's a great idea. I think it's a pity that spring can't control the destroy order, it really isn't doing all that much for me.
Chris
I looked at those 2 links, and i can't see how i could use the spring transaction manager (or anything else) to give me a session and transaction that i could inject into my actions, it only seemed to give me a session factory that i could then call 'getCurrentSession()' on, which to me doesn't look nice.
Chris
I guess what i'm asking is, if this isn't the best way, what *is* the best way to manage sessions / txns with spring?
Chris
Thanks for the update. So is the idea that i add those config changes, then add @Transactional to all my classes that need hibernate access, and then in those classes i have a getCurrentSession() ? But how would those classes then get the SessionFactory ?
Chris
Okay i've tried what you suggested, but now i have the issue that my services tier loads my event object fine, but when later on i try to access the fields in the returned object, it has a "LazyInitializationException - could not initialize proxy - no Session" error, presumably because the session has closed since my access to the services tier has finished. So i think i actually *do* still need a request-scoped session, somehow, what would you do in this situation?
Chris
Ah-ha! Got it working: needed OpenSessionInViewFilter set up in the web.xml. Phew!
Chris
+1  A: 

Transactions should be associated with services if you follow the Spring idiom. Sessions are web-tier objects, completely separate from the service tier. It sounds to me like you've made the mistake of entangling your web tier with the service tier. Better to tease them apart; you're unlikely to have this problem with that arrangement.

duffymo
Took me half a dozen reads of that to figure out what you meant! I now assume when you say 'service' you mean the 'business' layer classes (eg the classes that have stuff like 'FindEventById()'). So services are the ones that care about transactions. I guess that makes sense.
Chris
Okay i've tried what you suggested, but now i have the issue that my services tier loads my event object fine, but when later on i try to access the fields in the returned object, it has a "LazyInitializationException - could not initialize proxy - no Session" error, presumably because the session has closed since my access to the services tier has finished. So i think the session still needs to be tied to the web request somehow, any suggestions how you overcame this?
Chris
Ah-ha! Got it working: needed OpenSessionInViewFilter set up in the web.xml. Phew!
Chris