I'm developing an application that receives various XML files containing different 'Individual' objects that I store in a database.
The goal is that all the files are read and treated. If a file is processed correctly it moves to the "processed" folder. If an exception is thrown it moves to an error folder.
The desired behaviour is that if an error occurs in one of the files, everything is rollbacked , nothing is saved in the database and all the files are copied to the error folder (also the already processed ones)
The copying of the folders probably can't be done using transactions so I do them manually...
The structure of my project is the following:
Technologies:
Hibernate : 3.5.0-Final
Spring : 3.1.1.RELEASE
Server : Tomcat 7
Database : SQL Server
I start from the idea that the best location to put the transaction is the service. I don't add the propagation property, since I want the default Property.REQUIRED behaviour:
#Transactional(rollbackFor = Exception.class)
private Feedback readIndividuals(File fileLocation) throws Exception {
System.out.println("Start reading individuals");
//Set the status of all database entries to DELETED
individualEntityService.setAllStatussesToDeleted();
}
final File individualsProcessedFolder = new File(individualsProcessedFolderLocation);
for (final File fileEntry : fileLocation.listFiles()) {
if (fileEntry.isDirectory()) {
readIndividuals(fileEntry, feedback);
} else {
individualReader.read(fileEntry.getAbsolutePath());
....
Here I start the transaction. The individualReader is a service that performs the actual reading of the file and the writing to the DB.
EDIT
Here the code of the IndividualReader where I call the add method in the EntityService:
#Override
#Transactional
public void read(String fileLocation) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(CDM.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
XMLInputFactory factory = XMLInputFactory.newInstance();
FileInputStream fileInputStream = new FileInputStream(fileLocation);
XMLStreamReader xmlr = factory.createXMLStreamReader(fileInputStream, ENCODING);
try {
xmlr.nextTag();
xmlr.require(XMLStreamConstants.START_ELEMENT, null, "CDM");
xmlr.nextTag();
while (xmlr.getEventType() == XMLStreamConstants.START_ELEMENT) {
JAXBElement<CDM.Individual> jaxbIndividual = unmarshaller.unmarshal(xmlr,
CDM.Individual.class);
CDM.Individual individual = jaxbIndividual.getValue();
Individual individualDO = individualBuilder.build(individual);
Set<Diploma> diplomas = diplomaBuilder.build(individual.getDiplomas(), individualDO);
Set<HealthCareProfessional> healthCareProfessionals = healthCareProfessionalBuilder.build(individual.getHCProfessionals());
individualDO.addHealthCareProfessionals(healthCareProfessionals);
individualDO.addDiplomas(diplomas);
LOG.debug("Adding individual with SSIN [" + individualDO.getSsin() + "] into DB");
Individual retrievedIndividual = individualEntityService.read(individualDO.getSsin());
if (retrievedIndividual != null) {
individualEntityService.remove(retrievedIndividual);
individualDO.setStatus(EntryStatus.UPDATED);
}
individualEntityService.add(individualDO);
LOG.debug("Individual with SSIN [" + individualDO.getSsin() + "] successfully added to DB");
LOG.debug(getIndividualXMLAsString(individualDO));
if (xmlr.getEventType() == XMLStreamConstants.CHARACTERS) {
xmlr.next();
}
}
} finally {
xmlr.close();
fileInputStream.close();
}
}
The lower level is the EntityService:
#Override
#Transactional
public void add(Individual individual) {
individualDao.addIndividual(individual);
}
This class doesn't do anything more than calling the DAO, I annotated it with the #Transactional annotation. Since the default is Propagation.REQUIRED it won't start a new physical transaction, but it will join the transaction of the service.
Finally we have the DAO:
#Transactional
public void addIndividual(Individual individual) {
em.persist(individual);
}
I also annotate this method with Transactional, with the same reason as above.
The Entity manager is autowired in the DAO using Spring:
#PersistenceContext
private EntityManager em;
The Entity Manager is defined in the applicationContext as follows:
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="individual"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.SQLServerDialect"/>
<property name="generateDdl" value="true"/>
<property name="showSql" value="false"/>
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
</props>
</property>
<property name="dataSource" ref="dataSource"/>
</bean>
Now everything compiles and deploys fine and works even as expected. But when I make one of the XML files corrupt all the files before the corrupt file end up in the DB and the transaction is not rollbacked.
I guess I must be missing something and probably my mistake is in the wrong usage of the combination #Transaction and the Spring EntityManager. I never use the explicit em.flush() to push the data to the DB. Maybe the em.persist is wrong and stores the data to the database and I can't recover from it...
Anyone has an idea of what I'm doing wrong? Help would be highly appreciated.
EDIT Hereby the complete context:
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="be.healthconnect.pwg" />
<task:annotation-driven />
<tx:annotation-driven />
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:/be/healthconnect/pwg/core/properties/pwg.properties</value>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${datasource.driver.class.name}" />
<property name="url" value="${datasource.url}" />
<property name="username" value="${datasource.username}" />
<property name="password" value="${datasource.password}" />
</bean>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="individual"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.SQLServerDialect"/>
<property name="generateDdl" value="true"/>
<property name="showSql" value="false"/>
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
</props>
</property>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="jpaVendorAdaptor"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf" />
</bean>
</beans>
The mistake I made was the following: #Transactional annotation had no effect because it annotated a private method. The proxy generator was ignoring it.
I found the solution in the Spring Manual chapter 10.5.6:
Method visibility and #Transactional
When using proxies, you should apply the #Transactional annotation only to methods with public visibility.
If you do annotate protected, private or package-visible methods with the #Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
Related
I am using simple "helloWorld"ish application to learn Spring,Hibernate and transaction management using AOP. But is not working as expected. I am getting exception in transaction management. Details as follows :-
Spring version 4.3.8
Hibernate version 5.2.10
HSQL DB version 2.3.4
Spring.xml look as follows
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" mode="aspectj"
transaction-manager="transactionManager" />
<!-- THIS IS COMMENTED. Without commenting same result. I TRIED USING HibernateTransactionManager. still got same result. -->
<!--
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean> -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:file:C:/ProjectRelated/softwares/hsqldb-2.3.4/hsqldb/data/FirstFile"/>
<property name="username" value="sa"/>
<property name="password" value="sys"/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" >
<array>
<value>com.kaushik.winnersoft.data</value>
</array>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="customerDAO" class="com.kaushik.winnersoft.dao.CustomerDAOImpl">
<property name="hibernateTemplate" ref="hibernateTemplate"></property>
</bean>
<bean id="customerManager" class="com.kaushik.winnersoft.CustomerManagerImpl">
<property name="customerDAO" ref="customerDAO"></property>
</bean>
DAOImpl class is
public class CustomerDAOImpl implements CustomerDAO {
private HibernateTemplate hibernateTemplate;
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
#Override
#Transactional
public void create(Customer customer) {
System.out.println("in dao creating");
hibernateTemplate.save(customer);
System.out.println("in dao creating done");
}
I get output as follows
Doing
in dao creating
Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
at org.springframework.orm.hibernate5.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1165)
at org.springframework.orm.hibernate5.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:643)
at org.springframework.orm.hibernate5.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:640)
at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:359)
at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:326)
at org.springframework.orm.hibernate5.HibernateTemplate.save(HibernateTemplate.java:640)
at com.kaushik.winnersoft.dao.CustomerDAOImpl.create(CustomerDAOImpl.java:27)
at com.kaushik.winnersoft.CustomerManagerImpl.createCustomer(CustomerManagerImpl.java:20)
at com.kaushik.winnersoft.SpringTest.main(SpringTest.java:14)
Answer
Based on the coments given below by M. Denium; I did following changes and it worked.
1) Used HibernateTransactionManager instead of DataSourceTransactionManager
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
2) In removed mode="aspectj"
so it looks like
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
I see to problems with your configuration
You are not using the proper PlatformTransactionManager
Unless you are using load- or compile time weaving I doubt the mode="aspectj" is really correct.
First you need to use the PlatformTransactionManager that supports your persistence technology, as you are using Hibernate 5, you need to use the org.springframework.orm.hibernate5.HibernateTransactionManager instead of the DataSourceTransactionManager. (the latter is for application that only do plain JDBC transactions).
From the <tx:annotation-driven /> remove the mode="aspectj" as I suspect you are actually not using full blown AspectJ but are relying on plain Spring to do this for you.
Pro Tip: Instead of using HibernateTemplate, which isn't recommended anymore since Hibernate 3.0.1, just use plain SessionFactory with getCurrentSession instead. This will allow you to write plain Hibernate daos. As suggested in the reference guide.
i am a beginner in spring ,i have a dao class in spring .but annotation # auto wired or # service is not working ,i have solved the issue by creating a bean in application context,what is the reason for annotation not working in spring .provided with "context:component-scan base-package=" also but annotations are not working
StudentDao
public interface StudentDao {
public int addStudent(StudentEntity s);
}
-----------------------------------
#Service("studentDaoImpl")
public class StudentDaoImpl implements StudentDao{
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public int addStudent(StudentEntity student) {
// TODO Auto-generated method stub
em.persist(student);
return student.getId();
}
}
------------------------------------------------
FascadeDaoImpl
public class FascadeControllerImpl implements FascadeController {
// #Autowired
private StudentDao studentDao;
private UserContext uc;
public void studentDao() {
studentDao=(StudentDao) uc.getContext().getBean("studentDao");
}
}
public int addStudent(StudentEntity student) {
studentDao();
return studentDao.addStudent(student);
}
ApplicationContext
<?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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="sms.spring.dao" />
<context:component-scan base-package="sms.spring.fascade" />
<context:component-scan base-package="sms.spring.entity" />
<context:annotation-config />
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean class="sms.spring.entity.StudentEntity" id="studentbean"/>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
</property>
<property name="url">
<value>jdbc:sqlserver://localhost;databaseName=dbstsms</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value>sa123</value>
</property>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml" />
<property name="persistenceUnitName" value="PERSUnit" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<bean id="fascadeController" class="sms.spring.fascade.FascadeControllerImpl"></bean>
<bean id="studentDao" class="sms.spring.dao.StudentDaoImpl"></bean>
<bean id="loginDao" class="sms.spring.dao.LoginDaoImpl"></bean>
<bean id="facultyDao" class="sms.spring.dao.FacultyDaoImpl"></bean>
<bean id="markDao" class="sms.spring.dao.MarkDaoImpl"></bean>
<bean id="notificationDao" class="sms.spring.dao.NotificationDaoImpl"></bean>
<tx:annotation-driven />
Please post the error that you are getting,Also from the code you have posted I can see that even though you have annotated the StudentDaoImpl class with #service annotation you are again defining the same in the xml configuration,Kindly use either annotation based config or xml based configuration.
Please check if your file has
<context:annotation-config/> is mentioned in the xml config(Not needed if component scan is enabled)
Check if component scan is enabled for the package
Please refer to this answer for the correct schema location
Question is not clear, but it looks like your controller should also be annotated with #Controller. All classes belonging to a Spring project that have autowired dependencies, need to be created by Spring. In short, you should never make a new of a Spring class and create them using the ApplicationContext#getBean() or when it gets injected by another class.
Moreover, bear in mind with the constructors as at the point of creation of the bean the autowired dependencies are null (not initialized) and you need to create an init() method annotated with #PostConstruct.
Two main causes are:
You must tell spring, where to scan your components (make sure the packages are right)
You don't have an implementation of an actual bean (make sure StudentDao have #Service too)
Exception from application log:
12:04:18,503 INFO ExceptionResolver:30 - [ org.springframework.dao.DeadlockLoserDataAccessException ] Hibernate flushing: could not update: [sero.chase.integration.Beans.Bean#1000]; SQL [update SCHM.v***240u_bean set prop1=?, prop2=?, prop3=?, prop4=?, prop5=?, prop6=?, prop7=?, prop8=?, prop9=?, prop10=?, prop11=?, prop12=?, prop13=?, prop14=?, prop15=?, prop16=?, prop17=?, prop18=?, prop19=?, prop20=?, prop21=?, where bean_id=?]; UNSUCCESSFUL EXECUTION CAUSED BY DEADLOCK OR TIMEOUT. REASON CODE 00C90088, TYPE OF RESOURCE 00000302, AND RESOURCE NAME SCHM.SAKT240 .X'000017'. SQLCODE=-913, SQLSTATE=57033, DRIVER=3.53.70; nested exception is com.ibm.db2.jcc.b.SqlException: UNSUCCESSFUL EXECUTION CAUSED BY DEADLOCK OR TIMEOUT. REASON CODE 00C90088, TYPE OF RESOURCE 00000302, AND RESOURCE NAME SCHM.SAKT240 .X'000017'. SQLCODE=-913, SQLSTATE=57033, DRIVER=3.53.70org.springframework.dao.DeadlockLoserDataAccessException: Hibernate flushing: could not update: [sero.chase.integration.Beans.Bean#1000]; SQL [update SCHM.v***240u_bean set prop1=?, prop2=?, prop3=?, prop4=?, prop5=?, prop6=?, prop7=?, prop8=?, prop9=?, prop10=?, prop11=?, prop12=?, prop13=?, prop14=?, prop15=?, prop16=?, prop17=?, prop18=?, prop19=?, prop20=?, prop21=?, where bean_id=?]; UNSUCCESSFUL EXECUTION CAUSED BY DEADLOCK OR TIMEOUT. REASON CODE 00C90088, TYPE OF RESOURCE 00000302, AND RESOURCE NAME SCHM.SAKT240 .X'000017'. SQLCODE=-913, SQLSTATE=57033, DRIVER=3.53.70; nested exception is com.ibm.db2.jcc.b.SqlException: UNSUCCESSFUL EXECUTION CAUSED BY DEADLOCK OR TIMEOUT. REASON CODE 00C90088, TYPE OF RESOURCE 00000302, AND RESOURCE NAME MWIAKT1 .SAKT240 .X'000017'. SQLCODE=-913, SQLSTATE=57033, DRIVER=3.53.70
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:265)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.orm.hibernate3.HibernateTransactionManager.convertJdbcAccessException(HibernateTransactionManager.java:805)
at org.springframework.orm.hibernate3.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:791)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:664)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy54.save(Unknown Source)
at sero.chase.integration.DaoImpl.ExampleDaoImpl.save(ExampleDaoImpl.java:151)
at sero.chase.business.BOImpl.ExampleBOImpl.save(ExampleBOImpl.java:191)
at sero.chase.ServicesImpl.ExampleServiceImpl.submitAnswer(ExampleServiceImpl.java:183)
at sero.chase.business.BusDelegatesImpl.ExampleBusDelegateImpl.gradeAnswer(ExampleBusDelegateImpl.java:578)
at sero.chase.presentation.Controller.ExampleController.gradeAnswer(ExampleController.java:326)
at sero.chase.presentation.Controller.ExampleController.SubmitAnswer(ExampleController.java:422)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:79)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:618)
at org.springframework.web.servlet.mvc.multiaction.MultiActionController.invokeNamedMethod(MultiActionController.java:471)
at org.springframework.web.servlet.mvc.multiaction.MultiActionController.handleRequestInternal(MultiActionController.java:408)
at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:153)
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:900)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:827)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:763)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1152)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1087)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:118)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain._doFilter(WebAppFilterChain.java:87)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:840)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:683)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:589)
at com.ibm.ws.wswebcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:534)
at com.ibm.ws.webcontainer.servlet.CacheServletWrapper.handleRequest(CacheServletWrapper.java:90)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:751)
at com.ibm.ws.wswebcontainer.WebContainer.handleRequest(WebContainer.java:1478)
at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:126)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:458)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewInformation(HttpInboundLink.java:387)
at com.ibm.ws.http.channel.inbound.impl.HttpICLReadCallback.complete(HttpICLReadCallback.java:102)
at com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:165)
at com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
at com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:136)
at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:196)
at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:751)
at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:881)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1497)
Caused by: com.ibm.db2.jcc.b.SqlException: UNSUCCESSFUL EXECUTION CAUSED BY DEADLOCK OR TIMEOUT. REASON CODE 00C90088, TYPE OF RESOURCE 00000302, AND RESOURCE NAME MWIAKT1 .SAKT240 .X'000017'. SQLCODE=-913, SQLSTATE=57033, DRIVER=3.53.70
at com.ibm.db2.jcc.b.bd.a(bd.java:679)
at com.ibm.db2.jcc.b.bd.a(bd.java:60)
at com.ibm.db2.jcc.b.bd.a(bd.java:127)
at com.ibm.db2.jcc.b.fm.b(fm.java:2132)
at com.ibm.db2.jcc.b.fm.c(fm.java:2115)
at com.ibm.db2.jcc.t4.db.k(db.java:353)
at com.ibm.db2.jcc.t4.db.a(db.java:59)
at com.ibm.db2.jcc.t4.t.a(t.java:50)
at com.ibm.db2.jcc.t4.tb.b(tb.java:200)
at com.ibm.db2.jcc.b.gm.Zb(gm.java:2445)
at com.ibm.db2.jcc.b.gm.e(gm.java:3287)
at com.ibm.db2.jcc.b.gm.Rb(gm.java:612)
at com.ibm.db2.jcc.b.gm.executeUpdate(gm.java:595)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.executeUpdate(WSJdbcPreparedStatement.java:768)
at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:23)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2399)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2303)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2603)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JTATransaction.commit(JTATransaction.java:135)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
... 50 more
Spring configuration:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
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/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<jee:jndi-lookup id="queueConFac" resource-ref="true" jndi-name="jms/queueConFac" />
<jee:jndi-lookup id="receiveQ" resource-ref="true" jndi-name="jms/receiveQ" />
<jee:jndi-lookup id="sendQ" resource-ref="true" jndi-name="jms/sendQ" />
<jee:jndi-lookup id="XA" resource-ref="true" jndi-name="jdbc/XA" />
<jee:jndi-lookup id="nonXA" resource-ref="true" jndi-name="jdbc/nonXA" />
<bean id="jmsTxManager"
class="org.springframework.transaction.jta.WebSphereUowTransactionManager"/>
<bean id="jmsDestResolver" class=" org.springframework.jms.support.destination.JndiDestinationResolver"/>
<bean id="exampleListener" class="sero.chase.integration.JMS.Services.JMSReceiver">
<property name="exampleAppBusDelegate" ref="exampleAppBusDelegate" />
</bean>
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer" >
<property name="connectionFactory" ref="queueConFac" />
<property name="destination" ref="receiveQ" />
<property name="messageListener" ref="exampleListener" />
<property name="transactionManager" ref="jmsTxManager" />
<property name="taskExecutor" ref="jmsTaskExecutor" />
</bean>
<bean id="jmsTaskExecutor"
class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
<property name="workManagerName" value="wm/default" />
</bean>
<bean id="jmsSender" class="sero.chase.integration.JMS.Services.JMSSender">
<property name="connectionFactory" ref="queueConFac" />
<property name="queue" ref="sendQ" />
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en" />
</bean>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language" />
</bean>
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
<property name="interceptors">
<list>
<ref bean="localeChangeInterceptor" />
</list>
</property>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="WEB-INF/resources/langSpecificText"/>
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles2.TilesView" />
</bean>
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles-def.xml</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/nonXA" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="nonXA" />
<property name="configLocation" value="classpath:/hibernate.cfg.nonXA.xml" />
<property name="entityInterceptor">
<bean class="sero.chase.integration.Hibernate.DB2Interceptor"/>
</property>
</bean>
<bean id="session.XA.Factory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="XA" />
<property name="configLocation" value="classpath:/hibernate.cfg.XA.xml" />
<property name="entityInterceptor">
<bean class="sero.chase.integration.Hibernate.DB2Interceptor"/>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transaction.XA.Manager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="session.XA.Factory" />
</bean>
<bean id="transactionAttributeSource"
class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
<property name="properties">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- App Bean Definitions (Two dao configurations excluding several other bean configurations are displayed below) -->
<bean id="exampleDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributeSource" ref="transactionAttributeSource" />
<property name="target">
<bean class="sero.chase.integration.PersistenceImpl.ExamplePersistenceImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</property>
</bean>
<bean id="exampleXADao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true">
<property name="transactionManager" ref="transaction.XA.Manager" />
<property name="transactionAttributeSource" ref="transactionAttributeSource" />
<property name="target">
<bean class="sero.chase.integration.PersistenceImpl.ExamplePersistenceImpl">
<property name="sessionFactory" ref="session.XA.Factory" />
</bean>
</property>
</bean>
</beans>
Hibernate Non XA configuration:
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
sero.chase.integration.Hibernate.DB2390Dialect
</property>
<property name="hibernate.default_schema">SCHM</property>
<property name="query.substitutions">yes 'Y', no 'N'</property>
<property name="jdbc.use_streams_for_binary">true</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
<property name="jta.UserTransaction">java:comp/UserTransaction</property>
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
<!--===============-->
<!-- mapping files -->
<!--===============-->
<mapping resource="sero/chase/integration/hbm/Example.hbm.xml" />
</session-factory>
</hibernate-configuration>
Hibernate XA configuration:
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
sero.chase.integration.Hibernate.DB2390Dialect
</property>
<property name="hibernate.default_schema">SCHMA</property>
<property name="query.substitutions">yes 'Y', no 'N'</property>
<property name="jdbc.use_streams_for_binary">true</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.CMTTransactionFactory </property>
<property name="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.WebSphereExtendedJTATransactionLookup</property>
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
<!--===============-->
<!-- mapping files -->
<!--===============-->
<mapping resource="sero/chase/integration/hbm/Example.hbm.xml" />
</session-factory>
</hibernate-configuration>
Code snippet from my service implementation class where most of the business logic happens:
public void someDeadlockCausingServiceMethod() {
//The read1() method below is going to executing a select hql statement on Table A once the call goes all the way down to the Dao layer.
List<SomeBeanInBusinessLayer> beanList = exampleBO.read1();
//Do some processing with the values obtained from the read1() method
...
//
//saveOrUpdate() method below is going to execute an update hql statement on Table A once the call goes all the way down to the Dao layer
//where the values from someBeanInBusinessLayer is going to be copied into someBeanInDaoLayer before saving.
exampleBO.saveOrUpdate(someBeanInBusinessLayer)
//The read2() method below is going to execute a select hql statement that contains two inner selects (on Table B, C and D) once the call goes all
//the way down to the Dao layer.
List<SomeBeanInBusinessLayer2> beanList2 = exampleBO.read2();
//Do some processing with the values obtained from the read2() method inside a for loop
for(SomeBeanInBusinessLayer2 s: beanList2) {
//Read values from table E
List<SomeBeanInBusinessLayer3> beanList3 = exampleBO.read3(s.getProp2());
SomeBeanInBusinessLayer2 someBeanInBusinessLayer2 = new SomeBeanInBusinessLayer2();
someBeanInBusinessLayer2.setProp1(s.getProp2());
someBeanInBusinessLayer2.setProp1(someBeanInBusinessLayer3.getProp2());
//... more processing...
//Below method will execute an insert hql on Table F
exampleBO.saveOrUpdate(someBeanInBusinessLayer2);
SomeBeanInBusinessLayer3 someBeanInBusinessLayer3 = new SomeBeanInBusinessLayer3();
someBeanInBusinessLayer3.setProp1(s.getProp5());
//... more processing...
//Below method will execute an insert hql on Table G
exampleBO.saveOrUpdate(someBeanInBusinessLayer3);
}
}
public void anotherDeadlockCausingServiceMethod() {
//The read1() method below is going to executing a select hql statement on Table A once the call goes all the way down to the Dao layer.
List<SomeBeanInBusinessLayer> beanList = exampleBO.read1();
//The read2() method below is going to executing a select hql statement on Table F once the call goes all the way down to the Dao layer.
List<SomeBeanInBusinessLayer2> beanList2 = exampleBO.read2();
//The read1() method below is going to executing a select hql statement on Table G once the call goes all the way down to the Dao layer.
List<SomeBeanInBusinessLayer3> beanList3 = exampleBO.read3();
//Do some processing with the values obtained...
//Do an update on Table A
exampleBO.saveOrUpdate(someBeanInBusinessLayer1)
//Do an update on Table F
exampleBO.saveOrUpdate(someBeanInBusinessLayer2)
}
Code snippet from my Dao layer1:
public void load(BeanDTO beanDTO) {
Object param1 = beanDTO.getBeanList().getProp1();
Object param2 = beanDTO.getBeanList().getProp2();
List<SomeBeanInDaoLayer> beanList = null;
Object[] params = {param1, param2};
UserTransaction ut = null;
try {
Context context = new InitialContext();
ut = (UserTransaction) context.lookup(Constant.USR_TRANSACTION);
ut.begin();
beanList = beanDao2.load(params);
ut.commit();
}
catch(Exception e) {
try {
ut.rollback();
}
catch(Exception e1) {
if(logger.isDebugEnabled()) {
logger.debug("DB Exception", e1);
}
}
int error = ExceptionResolver.resolve(e);
if(logger.isDebugEnabled()) {
logger.debug("DB Exception", e);
}
beanDTO.setErrorCode(error);
}
beanDTO.setBeanList(beanList);
}
public void save(BeanDTO beanDTO) {
List<SomeBeanInDaoLayer> beanList = beanDTO.getBeanList();
for(SomeBeanInDaoLayer bean: beanList) {
try {
Context context = new InitialContext();
ut = (UserTransaction) context.lookup(Constant.USR_TRANSACTION);
ut.begin();
beanDao2.save(bean);
ut.commit();
}
catch(Exception e) {
try {
ut.rollback();
}
catch(Exception e1) {
if(logger.isDebugEnabled()) {
logger.debug("DB Exception", e1);
}
}
int err = ExceptionResolver.resolve(e);
if(logger.isInfoEnabled()) {
logger.info("DB Exception", e);
}
beanDTO.setErrorCode(err);
}
}
}
Code snipper from my Dao Layer2:
public List<Bean> load(Object[] params) {
String hql = "from Bean where beanProp1 = ? and beanProp2 = ?";
return (List<Bean>) getHibernateTemplate().find(hql, params);
}
public void save(Bean bean) {
getHibernateTemplate().saveOrUpdate(bean);
}
This application is a testing system where in users can take test
concurrently.
Initially the transaction demarcation was not at my Dao layer but
say at my Service Implementation class (actually all the way at my
controller class) where several reads and updates were tied up into
one transaction within a begin-commit block. Since I was seeing
several deadlocks I moved the demarcation to Dao layer so that
there's only one hql statement between my begin-commit block to see
if it prevents the deadlock but have had no luck.
Tried setting properties like hibernate.connection.autocommit to
true, hibernate.transaction.flush_before_completion to true and
hibehibernate.transaction.auto_close_session to true but had no
luck.
Never a row read by one user is updated by another user. Each user
reads and updates different rows even though they access the same
DB2 tables. Only at the time of running the process for building a
set of questions for a test, two users would read the same rows if
they were taking the same type of test. It is very similar to the
someDeadlockCausingMethod described above where the test questions
are prepared from a set of tables that contain questions and
answers. From iterating through this result set inside a for-loop,
new rows are inserted into another table to save the details of each
question that will appear on an user's test. This step is necessary
in the application because even though two users take the same test,
a set of random questions are taken from the pool of all questions
for each user.
Now that the test is prepared for user taking the test, the next
logical step in the application is to read the rows from a table
that contain just the details of the questions pertaining to the
user taking the test. So a concurrent user would read the same table
during this process but never the same row. The user is presented
with one question at a time. Once the user answers a question, the
row that was read to get the question pertaining to just this user
will be updated with the answer choice. Again never the same rows
are getting updated for two concurrent users. This method is
analogous to anotherDeadlockCausingMethod() described above.
I hope you got an idea of what the application does. The fact that
never the same rows are read or updated by concurrent users
surprised me on how a resource can get locked. Then I figured out
that page-locking was in place for the tables getting updated. So I
went and asked the DBA if he could change it to row-locking on the
tables that were getting updated. He is concerned about the
performance overhead on DB2 to implement row-locking and is worried
if it may affect other applications using DB2. So he doesn't want to
do it unless I can't find any other solution.
Please forget the XA/JMS portion. Assume that portion is commented
out for time being. For the most part of the application non-XA
datasource is used where I see the deadlocks.
Can someone please tell me how do I go about resolving the deadlock? I want to understand what went wrong in the design. Any help is greatly appreciated in advance
As you are performing several insert,update,delete i would like to suggest you adding
<property name="hibernate.connection.autocommit" value="false"/>
And once all you queries executed successfully then do the commit manually connection.commit();
Maybe this could help you.
Finally I managed a clean run without a deadlock doing the following:
hibernate.connection.isolation = 2
Added 'for update of with cs' at the end of my select statements.
I was using the saveOrUpdate() hibernate method for both update and insert. Now instead I use save() for insert and update() for update.
One thing I didn't understand was why using 'with ur' at the end of my select statements didn't resolve the deadlocks in comparison to the 'with cs' I'm using now. Wondering has the isolation level of the database, which is 'rs' got to do anything with it?
I am using standard JPA transaction manager for my JPA transactions. However, now I want to add some JDBC entities which will share the same 'datasource'. How can I make the JDBC operations transactional with spring transaction? Do I need to switch to JTA transaction managers? Is it possible to use both JPA & JDBC transactional service with same datasource? Even better, is it possible to mix these two transactions?
UPDATE:
#Espen :
I have a dao extended from SimpleJdbcDaoSupport which uses getSimpleJDBCTemplate.update to insert a database row. When a RuntimeException is thrown from the service code, the transaction never rolls back when using JPATransactionManager. It does rollback when using DatasourceTransactionManager. I tried to debug the JPATransactionManager and seems that it never performs rollback on underlying JDBCConnection(I guess due to the fact that the datasource is not necessarily has to be JDBC for JPA). My configuration setup are exactly like you explained here.
Here are my test codes:
<context:property-placeholder location="classpath:*.properties"/>
<!-- JPA EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceXmlLocation"
value="classpath:/persistence-test.xml" />
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.HibernatePersistence" />
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
-->
<!-- Database connection pool -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
<property name="testOnBorrow" value="${database.testOnBorrow}" />
<property name="validationQuery" value="${database.validationQuery}" />
<property name="minIdle" value="${database.minIdle}" />
<property name="maxIdle" value="${database.maxIdle}" />
<property name="maxActive" value="${database.maxActive}" />
</bean>
<!-- Initialize the database -->
<!--<bean id="databaseInitializer" class="com.vantage.userGroupManagement.logic.StoreDatabaseLoader">
<property name="dataSource" ref="storeDataSource"/>
</bean>-->
<!-- ANNOTATION SUPPORT -->
<!-- Enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- JPA annotations bean post processor -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<!-- Exception translation bean post processor (based on Repository annotation) -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<!-- throws exception if a required property has not been set -->
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
<bean id="userService" class="com.rfc.example.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
<property name="contactDao" ref="contactDao"></property>
<property name="callRecordingScheduledProgramTriggerDAO" ref="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO"></property>
</bean>
<bean id="userDao" class="com.rfc.example.dao.UserDaoJPAImpl" />
<bean id="contactDao" class="com.rfc.example.dao.ContactDaoJPAImpl"></bean>
<bean id="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO" class="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAOJDBCImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
AND HERE IS THE DAO:
#Transactional
public class CallRecordingScheduledProgramTriggerDAOJDBCImpl extends SimpleJdbcDaoSupport implements CallRecordingScheduledProgramTriggerDAO{
private static final Log log = LogFactory.getLog(CallRecordingScheduledProgramTriggerDAOJDBCImpl.class);
#SuppressWarnings("unchecked")
public CallRecordingScheduledProgramTrigger save(
CallRecordingScheduledProgramTrigger entity) {
log.debug("save -> entity: " + entity);
String sql = null;
Map args = new HashMap();
String agentIdsString = getAgentIdsString(entity.getAgentIds());
String insertSQL = "insert into call_recording_scheduled_program_trigger" +
" ( queue_id, queue_id_string, agent_ids_string, caller_names, caller_numbers, trigger_id, note, callcenter_id, creator_id_string, creator_id) " +
" values(:queueId, :queueIdString, :agentIdsString, :callerNames, :callerNumbers, :triggerId, :note, :callcenterId , :creatorIdString, :creatorId )";
args.put("queueId", entity.getQueueId());
args.put("agentIdsString",agentIdsString);
args.put("callerNames", entity.getCallerNames());
args.put("queueIdString", entity.getQueueIdString());
args.put("callerNumbers", entity.getCallerNumbers());
args.put("triggerId", entity.getTriggerId());
args.put("note", entity.getNote());
args.put("callcenterId", entity.getCallcenterId());
args.put("creatorId", entity.getCreatorId());
args.put("creatorIdString", entity.getCreatorIdString());
sql = insertSQL;
getSimpleJdbcTemplate().update(sql, args);
System.out.println("saved: ----------" + entity);
return entity;
}
}
Here is the client code that calls the dao and throws exception (spring service)
#Transactional(propagation=Propagation.REQUIRED)
public void jdbcTransactionTest() {
System.out.println("entity: " );
CallRecordingScheduledProgramTrigger entity = new CallRecordingScheduledProgramTrigger();
entity.setCallcenterId(10L);
entity.setCreatorId(22L);
entity.setCreatorIdString("sajid");
entity.setNote(System.currentTimeMillis() + "");
entity.setQueueId(22);
entity.setQueueIdString("dddd");
String triggerId = "id: " + System.currentTimeMillis();
entity.setTriggerId(triggerId);
callRecordingScheduledProgramTriggerDAO.save(entity);
System.out.println("entity saved with id: " + triggerId );
throw new RuntimeException();
}
NOTE: the code works as expected when using DatasourceTransactionManager
UPDATE - 2:
Ok I have found the root cause of the problem. Thanks to Espen.
My entity manager configuration was like this(copied from spring pet-clinic app):
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceXmlLocation"
value="classpath:/persistence-test.xml" />
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.HibernatePersistence" />
</property>
</bean>
Then I changed it to like this:
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation"
value="classpath:/persistence-test.xml" />
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect" />
</bean>
</property>
</bean>
Now everything seems to be working! Can anyone explain the difference between these two approach ?
It's possible to mix JPA and JDBC code in the same transaction using the JpaTransactionManager.
A snippet from Spring 3's JavaDoc:
This transaction manager also supports
direct DataSource access within a
transaction (i.e. plain JDBC code
working with the same DataSource).
This allows for mixing services which
access JPA and services which use
plain JDBC (without being aware of
JPA)!
You should be aware though that JPA caches the queries and executes all of them at the end of a transaction. So if you want to persist some data inside a transaction with JPA and then retrieve the data with JDBC, it will not work without explicitely flushing the JPA's persistence context before you attempt to retreive it with JDBC code.
A code example that asserts with JDBC code that the JPA code deleted a row inside a transaction:
#Test
#Transactional
#Rollback(false)
public void testDeleteCoffeeType() {
CoffeeType coffeeType = coffeeTypeDao.findCoffeeType(4L);
final String caffeForte = coffeeType.getName();
coffeeTypeDao.deleteCoffeeType(coffeeType);
entityManager.flush();
int rowsFoundWithCaffeForte = jdbcTemplate
.queryForInt("SELECT COUNT(*) FROM COFFEE_TYPES where NAME = ?",
caffeForte);
assertEquals(0, rowsFoundWithCaffeForte);
}
And if you prefer to use the JpaTemplate class, just replace the entityManager.flush() with jpaTemplate.flush();
In response to Sajids' comment:
With Spring you can configure a transaction manager that supports both JPA and JDBC like this:
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Transaction manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa
.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
and Annotation-Driven version
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf);
return jpaTransactionManager;
}
In order to make it work, the JDBC queries must be executed with the JdbcTemplate or the SimpleJdbcTemplate class. In your case with the DAO that extends the SimpleJdbcDaoSupport, you should use the getSimpleJdbcTemplate(..) method.
And finally to let two DAO methods participate in the same transaction, call both DAO methods from a service class metho annotated with #Transactional. With the <tx:annotation-driven> element in your config, Spring will handle the transaction for you with the given transaction manager.
On the business layer:
public class ServiceClass {..
#Transactional
public void updateDatabase(..) {
jpaDao.remove(..);
jdbcDao.insert(..);
}
}
Edit 2:
Then something is wrong. It works for me exactly as specified in the Javadoc.
Does your entity manager has a datasource property like my bean below? It will only work as long you're injecting the same datasource into the entity manager and your extended JpaDaoSupport classes.
<bean id="entityManagerFactoryWithExternalDataSoure" primary="true"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor
.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<value>
hibernate.format_sql=true
</value>
</property>
</bean>
I've not really worked this out in detail yet as I've not mixed both JDBC and JPA but if you get your JDBC connection for an XA datasource then they are JTA transaction. So if you run your code in Stateless session bean for example with transaction turned on, then you automatically get both your Entities and JDBC managed by JTA.
EDIT
Here is an example code from Servlet
private #Resource DataSource xaDatasource;
private #Resource UserTransaction utx;
private #PersistenceUnit EntityManagerFactory factory;
public void doGet(HttpServletRequest req, HttpServletResponse res) ... {
utx.begin();
//Everything below this will be in JTA
Connection conn = xaDatasource.getConnection();
EntityManager mgr = factory.createEntityManager();
//Do your stuff
...
utx.commit();
}
Disclaimer: Code not tested.
Just realize this is not Spring but I'll leave it up anyway
I am trying to test an entity EJB3 with Spring.
The EJB itself does not uses Spring and I would like to keep duplications of the production JPA configuration minimal (ie not duplicating persistence.xml for exemple).
My unit tests seems to work but even though my unit tests should be transactionnal, data is persisted between the various test methods ...
Here is my entity :
package sample;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class Ejb3Entity {
public Ejb3Entity(String data) {
super();
this.data = data;
}
private Long id;
private String data;
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
My unit test :
package sample;
import static org.junit.Assert.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"/appContext.xml"})
#Transactional
public class Ejb3EntityTest {
#PersistenceContext
EntityManager em;
#Before
public void setUp() throws Exception {
Ejb3Entity one = new Ejb3Entity("Test data");
em.persist(one);
}
#Test
public void test1() throws Exception {
Long count = (Long) em.createQuery("select count(*) from Ejb3Entity").getSingleResult();
assertEquals(Long.valueOf(1l), count);
}
#Test
public void test2() throws Exception {
Long count = (Long) em.createQuery("select count(*) from Ejb3Entity").getSingleResult();
assertEquals(Long.valueOf(1l), count);
}
}
and my appContext.xml :
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" />
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="jotm" />
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<bean id="dataSource" class="org.enhydra.jdbc.standard.StandardXADataSource">
<property name="driverName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:mem:unittest;DB_CLOSE_DELAY=-1" />
<property name="user" value="" />
<property name="password" value="" />
<property name="transactionManager" ref="jotm" />
</bean>
<bean id="emf"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitPostProcessors">
<bean class="sample.JtaDataSourcePersistenceUnitPostProcessor">
<property name="jtaDataSource" ref="dataSource" />
</bean>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="database" value="H2" />
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.JOTMTransactionManagerLookup" />
<entry key="hibernate.transaction.auto_close_session" value="false" />
<entry key="hibernate.current_session_context_class" value="jta" />
</map>
</property>
</bean>
</beans>
When I run my test, test2 fails because it finds 2 entity where I expected only one (because the first one should have been rollbacked ...)
I have tried a lot of different configurations and this one seems to be the most comprehensive I can get ... I have no other ideas. Do you ?
When I was trying to integrate JOTM and Hibernate, I eventually ended up having to code my implementation of ConnectionProvider. Here is what it looks like right now: http://pastebin.com/f78c66e9c
Then you specify your implementation as the connection privider in hibernate properties and transactions magically start to work.
The thing is that the default connection provider calls getConnection() on the datasource. In you own implementation you call getXAConnection().getConnection(). This makes the difference
I managed to make it work using Bitronix instead of JOTM. Bitronix provides a LrcXADataSource that allows a non XA database to participate in the JTA transaction.
I think the issues were that H2 is not XA compliant and the enhydra StandardXADataSource does not make it magically so (I also ended using HSQLDB but that is unrelated to the issue).
Here is my spring context that works :
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Bitronix Transaction Manager embedded configuration -->
<bean id="btmConfig" factory-method="getConfiguration"
class="bitronix.tm.TransactionManagerServices">
<property name="serverId" value="spring-btm" />
<property name="journal" value="null" />
</bean>
<!-- create BTM transaction manager -->
<bean id="BitronixTransactionManager" factory-method="getTransactionManager"
class="bitronix.tm.TransactionManagerServices" depends-on="btmConfig,dataSource"
destroy-method="shutdown" />
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="BitronixTransactionManager" />
<property name="userTransaction" ref="BitronixTransactionManager" />
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<!-- DataSource definition -->
<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource"
init-method="init" destroy-method="close">
<property name="className" value="bitronix.tm.resource.jdbc.lrc.LrcXADataSource" />
<property name="uniqueName" value="unittestdb" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="3" />
<property name="allowLocalTransactions" value="true" />
<property name="driverProperties">
<props>
<prop key="driverClassName">org.hsqldb.jdbcDriver</prop>
<prop key="url">jdbc:hsqldb:mem:unittestdb</prop>
<prop key="user">sa</prop>
<prop key="password"></prop>
</props>
</property>
</bean>
<!-- Entity Manager Factory -->
<bean id="emf"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="database" value="HSQL" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.BTMTransactionManagerLookup" />
<entry key="hibernate.transaction.auto_close_session" value="false" />
<entry key="hibernate.current_session_context_class" value="jta" />
</map>
</property>
</bean>
Edit: (Sorry, seems I was only half awake when I wrote this paragraph. Of course you're right, everything should be rolled back by default.)
You could check what the transaction manager is really doing, for example by enabling debug output for it.
Assuming log4j:
log4j.logger.org.springframework.transaction=DEBUG
The transaction manager gives you very nice log output about created and joined transactions, and also about commits and rollbacks. That should help you find out what isn't working with your setup.
Add #Rollback annotation (from org.springframework.test.annotation), just after the #Transactional annotation as mentioned in the spring documentation.
#Rollback is a test annotation that is used to indicate whether a test-
managed transaction should be rolled back after the test method has
completed.
Consult the class-level Javadoc for
org.springframework.test.context.transaction.TransactionalTest-
ExecutionListener for an explanation of test-managed transactions.
When declared as a class-level annotation, #Rollback defines the default
rollback semantics for all test methods within the test class hierarchy. When
declared as a method-level annotation, #Rollback defines rollback semantics
for the specific test method, potentially overriding class-level default
commit or rollback semantics.
As of Spring Framework 4.2, #Commit can be used as direct replacement for
#Rollback(false).
Warning: Declaring #Commit and #Rollback on the same test method or on the
same test class is unsupported and may lead to unpredictable results.
This annotation may be used as a meta-annotation to create custom composed
annotations. Consult the source code for #Commit for a concrete example.