Announcement Announcement Module
Collapse
No announcement yet.
Spring/Hibernate CMT transaction participation problem Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring/Hibernate CMT transaction participation problem

    I have the following scenario:

    - A stateless session bean
    - One method on that SSB.
    - The SSB uses CMT and the method TX attribute is set to REQUIRED
    - A DAO which is wired using spring and has a transactional proxy

    I'm using WebSphere 5.1 and hibernate as ORM tool, and I have two hbm files. One parent.hbm.xml and one child.hbm.xml. The parent has a one to many relation with the child, and the lazy flag is set to true.

    The business logic inside the bean method looks like this:

    [SSB method]
    1. DAO dao = (DAO) beanFactory.getBean("dao");
    2. TransferObjectParent parent = dao.find(1);
    3. TransferObjectChild child = new TransferObjectChild();
    4. parent.addChild(child);

    Since lazy is set to true, the parent object returned from the dao is actually a hibernate proxy. From the moment I add the child to it in step 4, hibernate will want to update the child table automatically, adding a child which references its parent using a foreign key.

    My application context relating this scenario:

    Code:
    <bean id="db" class="org.springframework.jndi.JndiObjectFactoryBean">
     <property name="jndiName">
       <value>java:comp/env/jdbc/db</value>
      </property>
    </bean>
    
    <bean name="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
      <property name="dataSource">
        <ref bean="db" />
     </property>
    
     <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.DB2Dialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
        <prop key="hibernate.max_fetch_depth">1</prop>
        <prop key="hibernate.use_second_level_cache">false</prop>
        <prop key="hibernate.use_query_cache">false</prop>
      </props>
     </property>
    
     <property name="mappingResources">
       <list>
        <value>parent.hbm.xml</value>
        <value>child.hbm.xml</value>
       </list>
     </property>
    
    </bean>
    
    <bean id="tran" class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean" />
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
      <constructor-arg>
       <ref bean="tran" />
      </constructor-arg>
    </bean>
    
    <bean name="daoTarget" class="DAO">
       <property name="sessionFactory">
         <ref bean="sessionFactory" />
       </property>
    </bean>
    
    <bean id="dao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
         <ref bean="transactionManager" />
        </property>
        
        <property name="target">
         <ref bean="daoTarget" />
        </property>
    
         <property name="transactionAttributes">
          <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
          </props>
        </property>
    </bean>
    --------------------- Ok, now the problem:

    The problem is that spring closes the hibernate session right after performing step 1. Therefore, I get a "LazyInit" exception when I want to add the child in step 3. I find this not logical, since spring is only allowed to close the session AFTER the transaction commit.

    Agreed, the TX property of the DAO is set to REQUIRED. So the transaction commit will be right after the dao method executed, BUT ! Since there IS a running JTA transaction (the bean has a CMT) when I call the dao, I would think that the session must only be closed when the JTA transaction is commited, and that is NOT at the end of each dao call, but at the end of the bean method execution !

    To test this, I created a class called "Delegate". I move all the code from the bean method into this class, and I execute this class from the bean method. Like:

    [SSB method]
    1. Delegate delegate = (Delegate) beanFactory.getBean("delegate");
    2. delegate.process();

    [Delegate.process() method]
    1. DAO dao = (DAO) beanFactory.getBean("dao");
    2. TransferObjectParent parent = dao.find(1);
    3. TransferObjectChild child = new TransferObjectChild();
    4. parent.addChild(child);

    The application context would then be extended with this :

    Code:
    <bean name="delegateTarget" class="Delegate"/>
    
    <bean id="delegate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
      <property name="transactionManager">
       <ref bean="transactionManager" />
      </property>
     
      <property name="target">
       <ref bean="delegateTarget" />
      </property>
    
      <property name="transactionAttributes">
       <props>
         <prop key="*">PROPAGATION_REQUIRED</prop>
       </props>
     </property>
    </bean>
    Now the lazy loading works, since the session remains open !
    So it seems that spring considers the transaction end as the latest defined transactional object in its application context.

    Now, what I want is that the session remains open until the container demarcated transaction is commit

    Any one ideas ?

    BTW:

    1. I'm sure that there is a JTA transaction. If I set the TX attribute of the DAO to MANDATORY and I set the TX attribute for the sessionbean to NEVER, then I get an exception from spring complaining that there is no TX

    2. I'm also sure that the DAO participates in the transaction. When I set the dao TX attribute to REQUIRES_NEW and I alter the code inside the session bean method:

    1. DAO dao = (DAO) beanFactory.getBean("dao");
    2. TransferObjectParent parent = new TransferObjectParent();
    3. dao.insert(parent);
    4. throw new RuntimeExceptio();

    then the parent IS inserted in the database. When I do this with the TX attribute set to REQUIRED, the parent IS NOT inserted into the database. Which is like it should. When TX is requires new, it should insert ignoring the exceptions that may follow after the insert transaction. When the TX is required, the insert should be rolledback is exceptions occur after the insert.

    Thanks for reading.

  • #2
    You need to also instruct the HB sessionFactory to use the JTA transaction manager - you can find more details in the LocalSessionFactoryBean javadocs and on Hibernate reference documentation. Turn on logging and make sure thath the container TM is used.

    Comment


    • #3
      try HibernateInterceptor

      Here is my experience:

      Move all business logic code from your SLSB to a service bean (your "Delegate"), interface is MyService, impl class is MyServiceImpl, and use HibernateInterceptor to handle transaction for Hibernate.

      The context xml is

      Code:
      <beans>
          <!-- real service impl bean -->
          <bean id="myServiceTarget" class="MyServiceImpl">
              <property name="myDao" ref="myDao"/>
          </bean>
      
          <!-- intercepted service -->
          <bean id="myService" class="org.springframework.aop.framework.ProxyFactoryBean">
               <property name="target"><ref bean="myServiceTarget"/></property>
               <property name="proxyInterfaces">
                 <value>MyService</value>
               </property>
               <property name="interceptorNames">
                 <list>
                    <value>hibernateInterceptor</value>
                 </list>
               </property>
           </bean>
           
          <!-- DAO -->
          <bean id="myDao" 
            class="MyHibernateDao">
          	<property name="sessionFactory" ref="sessionFactory"/>
          </bean>
      
          <!-- Hibernate Session Factory Bean -->
          <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
              <property name="mappingResources">
                  <list>
                      <value>MyObject.hbm.xml</value>
                  </list>
              </property>
              <property name="hibernateProperties">
                  <props>
                      <prop key="hibernate.dialect">org.hibernate.dialect.DB2Dialect</prop>
                      <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
                      <prop key="hibernate.max_fetch_depth">3</prop>
                      <prop key="hibernate.show_sql">true</prop>
                  </props>
              </property>
              <property name="dataSource">
                  <ref local="dataSource"/>
              </property>
          </bean>
          
          <bean id="jndiDataSource" class="org.springframework.jndi.JndiObjectFactoryBean" >
          	<property name="jndiName"><value>jdbc/MYDB</value></property>
          </bean>
          
          <bean id="dataSource" class="org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter">
              <property name="targetDataSource"><ref bean="jndiDataSource"/></property>
              <property name="username"><value>admin</value></property>
              <property name="password"><value>pass</value></property>
          </bean>
      
          <!-- HibernateInterceptor -->
          <bean id="hibernateInterceptor" class="org.springframework.orm.hibernate3.HibernateInterceptor">
              <property name="sessionFactory">
                  <ref bean="sessionFactory"/>
              </property>
          </bean>
      </beans>
      And in SLSB, delegate all business processes to MyService, and set transaction to REQUIRED for the SLSB in WebSphere

      Code:
              <assembly-descriptor>
      		<container-transaction>
      			<method>
      				<ejb-name>MYEJBService</ejb-name>
      				<method-name>*</method-name>
      			</method>
      			<trans-attribute>Required</trans-attribute>
      		</container-transaction>
      	</assembly-descriptor>
      You can read "Professional Java Development with the Spring Framework" Chapter 11 to see how to use EJB in spring.

      Hope that helps!

      Yuan
      Last edited by yuanji; Feb 8th, 2006, 10:53 AM.

      Comment

      Working...
      X