views:

412

answers:

2

I'm using Hibernate with OpenSessionInViewInterceptor so that a single Hibernate session will be used for the entire HTTP request (or so I wish). The problem is that Spring-configured transaction boundaries are causing a new session to be created, so I'm running into the following problem (pseudocode):

  • Start in method marked @Transactional(propagation = Propagation.SUPPORTS, readOnly = false)
  • Hibernate session #1 starts
  • Call DAO method to update object foo; foo gets loaded into session cache for session #1
  • Call another method to update foo.bar, this one is marked @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    • Transaction demarcation causes suspension of current transaction synchronization, which temporarily unbinds the current Hibernate session
    • Hibernate session #2 starts since there's no currently-existing session
    • Update field bar on foo (loading foo into session cache #2); persist to DB
    • Transaction completes and method returns, session #1 resumes
  • Call yet another method to update another field on foo
    • Load foo from session cache #1, with old, incorrect value of bar
    • Update field foo.baz, persist foo to DB
    • foo.bar's old value overwrites the change we made in the previous step

Configuration looks like:

<bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor" autowire="byName">
    <property name="flushModeName">
        <value>FLUSH_AUTO</value>
    </property>
</bean>

<bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="myDataSource" />
</bean>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="useTransactionAwareDataSource" value="true" />
    <property name="mappingLocations">
        <list>
            <value>/WEB-INF/xml/hibernate/content.hbm.xml</value>
        </list>
    </property>


    <property name="lobHandler">
        <ref local="oracleLobHandler" />
    </property>
    <!--property name="entityInterceptor" ref="auditLogInterceptor" /-->

    <property name="hibernateProperties"
        ref="HibernateProperties" />

    <property name="dataSource" ref="myDataSource" />

</bean>

I've done some debugging and figured out exactly where this is happening, here is the stack trace:

Daemon Thread [http-8080-1] (Suspended (entry into method doUnbindResource in TransactionSynchronizationManager))   
TransactionSynchronizationManager.doUnbindResource(Object) line: 222    
TransactionSynchronizationManager.unbindResource(Object) line: 200  
SpringSessionSynchronization.suspend() line: 115    
DataSourceTransactionManager(AbstractPlatformTransactionManager).doSuspendSynchronization() line: 620   
DataSourceTransactionManager(AbstractPlatformTransactionManager).suspend(Object) line: 549  
DataSourceTransactionManager(AbstractPlatformTransactionManager).getTransaction(TransactionDefinition) line: 372    
TransactionInterceptor(TransactionAspectSupport).createTransactionIfNecessary(TransactionAttribute, String) line: 263   
TransactionInterceptor.invoke(MethodInvocation) line: 101   
ReflectiveMethodInvocation.proceed() line: 171  
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204   
$Proxy14.changeVisibility(Long, ContentStatusVO, ContentAuditData) line: not available  

I can't figure out why transaction boundaries (even "nested" ones - though here we're just moving from SUPPORTS to REQUIRED) would cause the Hibernate session to be suspended, even though OpenSessionInViewInterceptor is in use.

When the session is unbound, I see the following in my logs:

[2010-02-16 18:20:59,150] DEBUG org.springframework.transaction.support.TransactionSynchronizationManager Removed value [org.springframework.orm.hibernate3.SessionHolder@7def534e] for key [org.hibernate.impl.SessionFactoryImpl@693f23a2] from thread [http-8080-1]
+1  A: 

First, your openSessionInViewInterceptor must have a sessionFactory injected, otherwise it can't do its job:

<property name="sessionFactory">
    <ref bean="sessionFactory" />
</property>

Also, there is a property called singleSession - it is true by default, but debug its value just in case.

Then, if using Spring-MVC, you have to configure the interceptor for the SimpleUrlHandlerMapping (or whichever you are using), so that it can be actually applied:

<property name="interceptors">
    <list>
       <ref bean="openSessionInViewInterceptor"/>
    </list>
</property> 

If using anything else, I think you have to define it using <aop> tags (what web framework are you using?)

Bozho
The singleSession property on openSessionInViewInterceptor is true by default. I've tried switching it to false, which solved this particular problem; but then I got a number of errors about associating collections with more than one session.The sessionFactory bean is getting auto-wired into the OSIVI byName. Explicitly adding it as a property makes no difference in the behavior.The interceptor is configured to be used as part of our ParameterizedUrlHandlerMapping. I didn't include that because it's custom. But suffice it to say the interceptor is indeed being used.
Kelly Ellis
A: 

I have this exact same problem. I had first thought that DB transaction boundaries drove creation of hibernate sessions. After a bit of debugging I realize now that I don't really understand them -- or how they are 'supposed' to be setup.

I'm using spring and a @Transactional service with two associated DAOs. I'm also using the default propagation (REQUIRED) across the board.

public class MyService {
 public MyPersonDao personDao;  // injected by spring
 public MyAddressDao addressDao; // injected by spring

 @Transactional
 public void create(Person p) {
  Address a = addressDao.findOrCreate(p.getAddressData());
  boolean inSession = personDao.getHibernateTemplate.contains(a);  // false

  p.setAddress(adressDao.create();

  personDao.store(p); // fails because a is transient
 }
}

From what I see in my logs, it looks like function calls through transactional proxies seem to open and close hibernate sessions.

Justin