Spring @Transaction无法使用LocalContainerEntityManagerFactoryBean

时间:2013-12-06 22:36:12

标签: java sql-server spring hibernate transactions

我正在开发一个应用程序,它接收包含我存储在数据库中的不同“个人”对象的各种XML文件。 目标是读取和处理所有文件。如果文件处理正确,则会移至“已处理”文件夹。如果抛出异常,则会移动到错误文件夹。

所需的行为是,如果其中一个文件发生错误,所有内容都会回滚,数据库中没有保存任何内容,所有文件都会复制到错误文件夹(也是已经处理过的文件夹)

使用交易可能无法复制文件夹,所以我手动完成...

我的项目结构如下:

project structure

技术:

  • Hibernate:3.5.0-Final
  • Spring:3.1.1.RELEASE
  • 服务器:Tomcat 7
  • 数据库:SQL Server

我从这个想法开始,即交易的最佳位置是服务。我没有添加传播属性,因为我想要默认的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>

1 个答案:

答案 0 :(得分:0)

我犯的错误如下:@Transactional注释没有效果,因为它注释了一个私有方法。代理生成器忽略了它。

我在Spring Manual chapter 10.5.6中找到了解决方案:

  

方法可见性和@Transactional

     

使用代理时,您应该仅将@Transactional注释应用于具有公共可见性的方法。   如果使用@Transactional注释对带保护的,私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示已配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。