我正在开发一个应用程序,它接收包含我存储在数据库中的不同“个人”对象的各种XML文件。 目标是读取和处理所有文件。如果文件处理正确,则会移至“已处理”文件夹。如果抛出异常,则会移动到错误文件夹。
所需的行为是,如果其中一个文件发生错误,所有内容都会回滚,数据库中没有保存任何内容,所有文件都会复制到错误文件夹(也是已经处理过的文件夹)
使用交易可能无法复制文件夹,所以我手动完成...
我的项目结构如下:
技术:
我从这个想法开始,即交易的最佳位置是服务。我没有添加传播属性,因为我想要默认的Property.REQUIRED行为:
@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());
....
我在这里开始交易。 individualReader是一种服务,它执行文件的实际读取和对DB的写入。
修改 这里是IndividualReader的代码,我在EntityService中调用add方法:
@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();
}
}
较低级别是EntityService:
@Override
@Transactional
public void add(Individual individual) {
individualDao.addIndividual(individual);
}
除了调用DAO之外,这个类没有做任何事情,我用@Transactional注释注释了它。由于默认值为Propagation.REQUIRED,因此它不会启动新的物理事务,但会加入服务的事务。
最后我们有了DAO:
@Transactional
public void addIndividual(Individual individual) {
em.persist(individual);
}
我还使用Transactional注释此方法,原因与上述相同。 实体管理器使用Spring在DAO中自动装配:
@PersistenceContext
private EntityManager em;
实体管理器在applicationContext中定义如下:
<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>
现在一切都可以编译和部署,并且可以按预期工作。但是当我让其中一个XML文件损坏所有文件之前,损坏的文件最终都在数据库中并且事务没有回滚。
我想我必须遗漏一些东西,可能我的错误在于组合@Transaction和Spring EntityManager的错误用法。我从不使用显式的em.flush()将数据推送到数据库。也许em.persist是错误的并将数据存储到数据库中,我无法从中恢复......
任何人都知道我做错了什么?帮助将受到高度赞赏。
编辑以下是完整的背景信息:
<?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>
答案 0 :(得分:0)
我犯的错误如下:@Transactional注释没有效果,因为它注释了一个私有方法。代理生成器忽略了它。
我在Spring Manual chapter 10.5.6中找到了解决方案:
方法可见性和@Transactional
使用代理时,您应该仅将@Transactional注释应用于具有公共可见性的方法。 如果使用@Transactional注释对带保护的,私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示已配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。