Announcement Announcement Module
Collapse
No announcement yet.
Spring transaction synchronization, CMT EJB and Hibernate Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring transaction synchronization, CMT EJB and Hibernate

    Summary:

    I'm getting a LazyInitializationException when I navigate a domain object graph which is loaded deep within Spring-managed beans from an EJB. It appears that the the boundary of Spring's transaction synchronization callbacks is around the Spring invocation stack rather than the initial transaction created for the EJB, causing the Hibernate session to be closed before control is passed back to the EJB.

    Details:

    My application is an Java EE 5 application which is built on Spring 2.5.x and Hibernate 3.3.x. The collaborators are:

    EJB 3.0 SLSB Facade ->
    Spring Manager (obtained via Bean Factory) ->
    Spring DAO (extends HibernateDaoSupport) ->
    Hibernate

    The SLSB Facade does nothing except to delegate to the appropriate Manager bean a la the Application Service pattern. It uses CMT and has a transaction attribute of TX_REQUIRED. The Manager has a TransactionInterceptor defined as follows:

    Code:
    <bean id="serviceAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="interceptorNames">
            <list>
                <value>serviceTxInterceptor</value>
            </list>
        </property>
        <property name="beanNames">
            <list>
                <value>testManager</value>
            </list>
        </property>
    </bean>
    
    <bean id="serviceTxAttributes" class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
        <property name="properties">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    
    <bean id="serviceTxInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref bean="transactionManager" />
        </property>
        <property name="transactionAttributeSource">
            <ref bean="serviceTxAttributes" />
        </property>
    </bean>
    
        <!-- This example uses JBoss -->
    <jee:jndi-lookup id="jtaTransactionManager" jndi-name="java:/TransactionManager" cache="true"
        lookup-on-startup="false" expected-type="javax.transaction.TransactionManager" proxy-interface="javax.transaction.TransactionManager" />
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="userTransactionName">
            <null />
        </property>
        <property name="transactionManager" ref="jtaTransactionManager" />
    </bean>
    The Manager and DAO are configured as follows:

    Code:
    <bean id="testManager" class="com.example.testmanager.service.TestManager">
        <property name="testDao" ref="testDao" />
    </bean>
    
    <bean id="testDao" class="com.example.testmanager.dao.TestDao">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    The relevant Hibernate configuration is below. This example uses a PostgreSQL data source. Parent and Child are in a one-to-many composite relationship.

    Code:
    <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>
    
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDataSource" />
    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource">
            <ref bean="dataSource" />
        </property>
        <property name="mappingResources">
            <list>
                <value>com/example/testmanager/domain/Parent.hbm.xml</value>
                <value>com/example/testmanager/domain/Child.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
                <prop key="hibernate.connection.release_mode">auto</prop>
                <prop key="hibernate.connection.isolation">1</prop>
                <prop key="hibernate.default_schema">public</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
                <prop key="hibernate.jdbc.batch_size">15</prop>
                <prop key="hibernate.jdbc.batch_versioned_data">true</prop>
                <prop key="hibernate.max_fetch_depth">1</prop>
                <prop key="hibernate.transaction.factory_class">org.hibernate.transaction.CMTTransactionFactory</prop>
                <prop key="hibernate.transaction.flush_before_completion">true</prop>
                <prop key="hibernate.use_outer_join">true</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">$false</prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
            </props>
        </property>
        <property name="jtaTransactionManager" ref="jtaTransactionManager" />
    </bean>
    The scenario under discussion is:

    1. EJB looks up testManager from BeanFactory.
    2. EJB calls a method on testManager.
    3. testManager calls a method on injected testDao.
    4. DAO performs query (returning, say, a list of Parent domain objects)
    6. testManager returns those Parent domain objects to EJB
    7. EJB iterates over the Parents and tries to navigate to a Parent's list of Children. A LazyInitializationException results indicating that the Hibernate session was closed.

    The logs below are an example of the scenario on JBoss. I've enabled logging for org.springframework.orm.hibernate3, org.springframework.transaction

    Code:
    <<EJB method calls Spring service>>
    [org.springframework.transaction.jta.JtaTransactionManager] Participating in existing transaction
    [org.springframework.transaction.support.TransactionSynchronizationManager] Initializing transaction synchronization
    [org.springframework.transaction.interceptor.TransactionInterceptor] Getting transaction for [com.example.testmanager.service.TestManager.testMethod]
    [org.springframework.orm.hibernate3.SessionFactoryUtils] Opening Hibernate Session
    [org.springframework.orm.hibernate3.SessionFactoryUtils] Registering Spring transaction synchronization for new Hibernate Session
    [org.springframework.transaction.support.TransactionSynchronizationManager] Bound value [org.springframework.orm.hibernate3.SessionHolder@1acbf5c] for key [org.hibernate.impl.SessionFactoryImpl@18a79e] to thread [WorkerThread#0[127.0.0.1:3670]]
    <<DAO method>>
    [org.springframework.transaction.interceptor.TransactionInterceptor] Completing transaction for [com.example.testmanager.service.TestManager.testMethod]
    [org.springframework.transaction.jta.JtaTransactionManager] Triggering beforeCommit synchronization
    [org.springframework.orm.hibernate3.SessionFactoryUtils] Flushing Hibernate Session on transaction synchronization
    [org.springframework.transaction.jta.JtaTransactionManager] Triggering beforeCompletion synchronization
    [org.springframework.transaction.support.TransactionSynchronizationManager] Removed value [org.springframework.orm.hibernate3.SessionHolder@1acbf5c] for key [org.hibernate.impl.SessionFactoryImpl@18a79e] from thread [WorkerThread#0[127.0.0.1:3670]]
    [org.springframework.orm.hibernate3.SessionFactoryUtils] Closing Hibernate Session
    [org.springframework.transaction.jta.JtaTransactionManager] Triggering afterCommit synchronization
    [org.springframework.transaction.jta.JtaTransactionManager] Registering after-completion synchronization with existing JTA transaction
    [org.springframework.transaction.support.TransactionSynchronizationManager] Clearing transaction synchronization
    <<return to EJB method>>
    <<lazy initialization exception occurs here>>
    What appears to happen is that the SessionFactoryUtils registers a transaction synchronization with the transaction synchronization manager, and this callback gets invoked when the transaction "commits" from the Spring perspective. The session flushes in the beforeCommit phase and is closed in the beforeCompletion phase.

    I expected that the Hibernate session would be synchronized with the governing JTA transaction, not the lifecycle of Spring's TransactionManager and TransactionSynchronizationManager.

    There are of course a couple of ways to work around this that I've tested successfully:

    1. The EJB simply does a SessionFactoryUtils.getSession() using the sessionFactory bean loaded from the BeanFactory. It doesn't do anything with the session, but this does result in SessionFactoryUtils registering a JTA and not a Spring transaction synchronization. This could be extended to an EJB 3 Interceptor.
    2. Remove the serviceTxInterceptor entirely from the testManager. This would be OK if we never took advantage of read-only hints or never used PROPAGATION_REQUIRES_NEW.

    There were also some things that didn't work:

    1. Set the transactionSynchronization on the JtaTransactionManager to SYNCHRONIZATION_ON_ACTUAL_TRANSACTION. This made no difference at all.
    2. Using an explicit Hibernate transaction manager lookup in the Hibernate configuration. This doesn't work since SessionFactoryUtils overrides it anyway.
    3. Programatically obtaining a transaction from the jtaTransactionManager bean in the EJB. This solution would work, but you'd have to also explicitly close the Spring transaction obtained for synchronization to take place.

    What I'd like to know is, is this the expected behaviour? Does the Spring "stack" of a container-initiated transaction exist with its own lifecycle and must synchronization occur when leaving the Spring stack? Is there a way to configure Spring so that it works the way I expected?

  • #2
    I doubt anyone knows what the proper behavior is suppose to be with JTA transaction manager. If you look at the source code it has been around since spring 1.2, around 2003. I am not even sure it still works properly with latest hibernate 3.5+

    Comment

    Working...
    X