2
votes

We were using Spring managed transactions fine with:

JBoss 5.1.0.GA Spring.2.5.6 Hibernate.3.5.6 (using natively).

Upgraded to Spring 3.2.6 (needed to upgrade because there were some issues with JBoss 5, Spring 2.5.6 and using Autowiring for Dependency Injection, that is now solved). After the Spring upgrade, Spring managed Transaction's are no longer kicking in.

Here is the services-context.xml file:

<?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"
  xmlns:jee="http://www.springframework.org/schema/jee"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/jee 
            http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
            http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:annotation-config/>
  <context:component-scan base-package="com.mycompany, com.mycompany.imports"/>  

  <jee:jndi-lookup id="dataSource" jndi-name="java:/myDB"/>

  <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="hibernate.cfg.xml"/>
  </bean>

  <bean id="benefitsRepository" class="com.mycompany.repository.impl.BenefitsHibernateRepository">
    <property name="sessionFactory" ref="hibernateSessionFactory"/>
  </bean>

  <!-- ******************* Configure Transactions ************************** -->

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

  <aop:config>
    <aop:pointcut id="serviceOperation" expression="execution(* com.mycompany.service..*Service.*(..))"/>
    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
  </aop:config>

  <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="search*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
</beans>

The Service class where we want to inject Spring Configured Transaction looks like this:

package com.mycompany.service.impl;

@Service
public class BenefitsServiceImpl implements BenefitsService
{
    @Autowired
    protected BenefitsRepository        _benefitsRepository;    

    public void assignBenefitToEmployee(Employee employee, Long benefitId)
    {
        // JTA Transaction should begin here

        // We load persistent benefit object (proxy) no database call made yet
        // Note: Spring's HibernateTemplate is going to open Hibernate session/transaction here   
        Benefit benefit = _benefitsRepository.loadBenefit(benefitId);     

        // Hibernate session/transaction is already closed and further calls on proxy are causing LazyInitializationException
        // This is where Hibernate LazyInitializationException gets thrown
        benefit.getEmployees();  

        // JTA Transaction should end here (but obviously not the case as the hibernate session is already closed!)             
    }
}

The above code worked fine with Spring 2.5.6, however after the upgrade to Spring 3.2.6 the transactions are no longer being injected. The default behavior of HibernateTemplate is to begin/end transaction when a call is made via getHibernateTempalte().get/find calls, unless a JTA or some other transaction already exists, in which case it will propagate it.

Before the Spring upgrade, JTA transaction was present at service level, so session remained open through out the service call (in our case assignBenefitToEmployee() method call). But now since the transaction is no longer taking affect the Hibernate session is closing at the Repository level and thus causing LazyInitializationException.

We can solve the problem easily by making following change

_benefitsRepository.loadBenefit(benefitId); // want this to work
// change to :
_benefitsRepository.getBenefit(benefitId); // works

Which obviously works, but the goal is to enable transaction at the service level. We are using this as a test case to make sure the transactions are properly configured.

For completeness, this is what the Repository looks like:

package com.mycompany.repository.impl;

public class BenefitsHibernateRepository extends HibernateDaoSupport implements BenefitsRepository
{

    public Benefit loadBenefit(Integer benefitId)
    {
        return (Benefit) getHibernateTemplate().load(Benefit.class, benefitId);
    }
}

Let me re-iterate, all of this worked perfectly with Spring 2.5.6. The only things that changed are:

  1. Upgrade Spring 2.5.6 -> 3.2.6
  2. Autowire some of the beans (before we were not using any annotations/autowiring)

I have also tried configuring the transactions through

<context:annotation-config/>
<context:component-scan base-package="com.mycompany, com.mycompany.imports"/> 
<tx:annotation-driven transaction-manager="txManager"/>

But that has not worked either.

Also tried changing transaction type from JTA to Hibernate:

  <!--  
  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
  -->
  <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="hibernateSessionFactory"/>
  </bean>

This has not worked either. Running out of options, any ideas??

Here is some additional information about how our EAR is structured and how Spring is initialized:

MyApp.ear:

  • FrontEnd.war (Spring MVC)
    • /WEB-INF/dispatcher-servlet.xml
    • /WEB-INF/applicationContext.xml (import's another applicationContext.xml present in EJBs.jar project)
  • WebServices.war
  • CoreServices.jar (Hibernate)
    • services-context.xml (The one shown above, trying to configure transactions in)
    • beanRefContext.xml
  • EJBs.jar (EJB 2.1)
    • /com/myapp/applicationContext.xml

This is what the web.xml in FrontEnd.war looks like:

 <context-param>
     <param-name>parentContextKey</param-name>
     <param-value>myapp.ear.context</param-value>
    </context-param>

    <listener>
      <listener-class>
        org.springframework.web.context.ContextLoaderListener
      </listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/applicationContext.xml
                /WEB-INF/dispatcher-servlet.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

Here is how the beanRefContext.xml in CoreServices.jar looks like (just realized it is still pointing to Spring 2.5.6 xsd schema's which may be an issue?)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- The context files listed here should contain beans that are used by all WARs, 
         for example Services, EJB's and DAO's etc. -->
    <bean id="com.myapp.services-context" class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
          <list>
            <value>services-context.xml</value>
          </list>
        </constructor-arg>


   <!-- Define an alias to treat services-context as the enterprise wide "EAR" context.
        This context is the parent ApplicationContext for the WebApplicationContexts defined in the WARs. 
        If we ever want to change this parent we simply assign this alias to another context. -->
   <alias name="com.myapp.services-context" alias="myapp.ear.context"/>


</beans>

The idea is to allow the services in CoreServices.jar be available to all the WAR's present in the EAR, that is why we are NOT initializing services-context.xml via the dispatcher-servlet in web.xml.

Note that we have also included the following in dispatcher-servlet.xml:

  <context:annotation-config/>
  <context:component-scan base-package="com.myapp"/>

This is to enable annotations in our Controllers in FrontEnd.war.

Hopefully this will give a better picture as to how Spring is initialized. Will appreciate any insights as to why Spring AOP or annotations based Transactions are not working in the CoreService.jar project. Again they used to work in Spring 2.5.6.

1
That seems like a pretty old version of JBoss. Are you sure it is compatible with Spring 3.2.6?CodeChimp
Don't know if there is any way to know. I know Spring Annotations don't work with JBoss 5.1.0.GA and Spring 2.5.6. The main reason why we upgraded.Sheraz Khan
2. Autowire some beans if it's an mvc app then please check that there is no excess <context:component-scan in the dispatcher servlet/mvc context, or some other type of bean duplication.Boris Treukhov
check if you can have variables starting with underscore _benefits . I think that is not recommendedArun
Boris, to answer your question we do have component-scan in dispatcher-servlet.xml as well. However I added more information above to display how Spring is initialized. I am assuming we need that in the Web Context to enable annotations in the Controllers?Sheraz Khan

1 Answers

0
votes

Thanks to Boris Treukhov's comment, removing extra <context:annotation-config/> from dispatcher-servlet.xml solved the problem!

So now the configuration looks like:

dispatcher-servlet.xml: (no component-scan)

 <context:annotation-config/>

services-context.xml:

<context:component-scan base-package="com.myapp, com.myapp.imports"/>

<jee:jndi-lookup id="dataSource" jndi-name="java:/myDB"/>

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

  <aop:config>
    <aop:pointcut id="serviceOperation" expression="execution(* com.myapp.service..*Service.*(..))"/>
    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
  </aop:config>

  <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="search*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

Autowiring, annotations, and transactions are working in all layers of the application!