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:
- Upgrade Spring 2.5.6 -> 3.2.6
- 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.
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