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>
<tx:annotation-driven>
? – Sotirios Delimanolisadd
. The most likely cause of something like this is inadvertently crossing a transaction boundary somewhere. – chrylis -cautiouslyoptimistic-