Spring @Transactional 即使抛出异常也提交部分结果

Spring @Transactional commiting partial results even exception is thrown

我是 运行 spring 下的 Apache CXF Web 服务。我使用 JPA 来保存信息。该服务有一个更新一系列行的方法。在保留每一行之前,我检查要保留的值是否确实存在于数据库中。如果存在不存在的值,则抛出 Exception。问题是我需要回滚所有更新的值。我虽然在我的 Web 服务方法中使用 @Transactional 可以解决问题,但取而代之的是,持久化的值实际上在数据库中被修改,这不是所需的行为。

这是网络服务方法的代码

@Transactional( propagation = Propagation.REQUIRED )
public UpdateDescriptionResponse updateDescription(UpdateDescriptionRequest updateDescriptionRequest) throws SIASFaultMessage {
    try {
        SubstanceEntity substance = service.findSubstanceBySubstanceID(updateDescriptionRequest.getUpdateDescriptionRequestData().getIdentity().getSubstanceID());
        if (substance!=null){
            for(DescriptionKeyValueType keyValue: updateDescriptionRequest.getUpdateDescriptionRequestData().getSubstanceDescriptionData() ){
                boolean descriptionExists = false;
                for(DescriptionEntity desc: substance.getDescriptionsById()){
                    if (desc.getDescKey().equals(keyValue.getKey())) {
                        descriptionExists = true;
                        break;
                    }
                }
                if (!descriptionExists){
                    SIASFaultDetail faultDetail = new SIASFaultDetail();
                    faultDetail.setSIASFaultDescription("Description key does not match given substance ID");
                    faultDetail.setSIASFaultMessage(SIASFaultCode.INVALID_INPUT.toString());
                    faultDetail.setSIASFaultType(SIASFaultCode.INVALID_INPUT);
                    SIASFaultMessage fault = new SIASFaultMessage("Description key does not match given substance ID", faultDetail);
                    throw fault;
                }
                else
                    descriptionLogic.updateDescription(substance.getSubstanceId(), keyValue.getKey(),keyValue.getValue());
            }
            UpdateDescriptionResponse response = new UpdateDescriptionResponse();
            UpdateDescriptionResponse.UpdateDescriptionResponsePackage responsePackage = new UpdateDescriptionResponse.UpdateDescriptionResponsePackage();
            ResponseStatus status = new ResponseStatus();
            status.setMessage(messageOk);
            status.setReturn(BigInteger.valueOf(0));
            responsePackage.setResponseStatus(status);
            response.setUpdateDescriptionResponsePackage(responsePackage);
            return response;
        }
        else
        {
            SIASFaultDetail faultDetail = new SIASFaultDetail();
            faultDetail.setSIASFaultDescription("Substance ID does not exists");
            faultDetail.setSIASFaultMessage(SIASFaultCode.INVALID_SUBSTANCE_ID.toString());
            faultDetail.setSIASFaultType(SIASFaultCode.INVALID_SUBSTANCE_ID);
            SIASFaultMessage fault = new SIASFaultMessage("Substance ID does not exists", faultDetail);
            throw fault;
        }
    } catch (SIASFaultMessage ex) {
        throw ex;
    } catch (Exception ex) {
        SIASFaultDetail a = new SIASFaultDetail();
        a.setSIASFaultDescription("Unknown error processing enroll request");
        a.setSIASFaultMessage("SERVICE_ERROR");
        a.setSIASFaultType(SIASFaultCode.UNKNOWN_ERROR);
        SIASFaultMessage fault = new SIASFaultMessage("Something happened", a);
        throw fault;
    }
}

这是 descriptionLogic.updateDescription(...)

实例的代码
@Override
public void updateDescription(String substanceID, String key, String value) {
    PageRequest page = new PageRequest(1, 1);
    Map<String, Object> filters = new HashMap<String, Object>();
    filters.put("SUBSTANCE_ID", substanceID);
    List<SubstanceEntity> substances = substanceService.findAll(page, filters);
    if (substances.size() == 0) {
        return;
    }
    SubstanceEntity substanceEntity = substances.get(0);

    for (DescriptionEntity desc : substanceEntity.getDescriptionsById()) {
        if (desc.getDescKey().equals(key)) {
            desc.setDescValue(value);
            descriptionService.persist(desc);
        }
    }
}

这是未通过的测试

@Test()
public void testUpdateDescription_does_not_modify_description_with_invalid_values() throws Exception {
    UpdateDescriptionRequest request = new UpdateDescriptionRequest();
    UpdateDescriptionRequest.UpdateDescriptionRequestData data = new UpdateDescriptionRequest.UpdateDescriptionRequestData();
    SIASIdentity identity = new SIASIdentity();
    identity.setSubstanceID("804ab00f-d5e9-40ff-a4d3-11c51c2e7479");
    data.getSubstanceDescriptionData().add(new DescriptionKeyValueType() {{
        setKey("KEY3_1");
        setValue("NEW_VALUE_1");
    }});
    data.getSubstanceDescriptionData().add(new DescriptionKeyValueType() {{
        setKey("KEY3_5");
        setValue("NEW_VALUE_2");
    }});
    data.setIdentity(identity);
    request.setUpdateDescriptionRequestData(data);

    try {
        siasService.updateDescription(request);
    }
    catch (SIASFaultMessage ex){

    }
    DescriptionEntity descriptionEntity1 = descriptionService.findById(1);
    DescriptionEntity descriptionEntity2 = descriptionService.findById(2);
    assertThat("The value does not math",descriptionEntity1.getDescValue(), not(equalTo("NEW_VALUE_1")));
    assertThat("The value does not math",descriptionEntity2.getDescValue(), not(equalTo("NEW_VALUE_2")));
    Assert.assertEquals("The description does not math","KEY3_1", descriptionEntity1.getDescKey());
    Assert.assertEquals("The description does not math","KEY3_2", descriptionEntity2.getDescKey());
}

这一行失败:

assertThat("The value does not math",descriptionEntity1.getDescValue(), not(equalTo("NEW_VALUE_1")));

这是我的 spring 上下文配置文件中的数据源配置

.
.
.
<bean id="myDataSource"
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<jdbc:initialize-database data-source="myDataSource">
    <jdbc:script location="classpath:test-data.sql" />
</jdbc:initialize-database>

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="packagesToScan" value="cu.jpa"/>
    <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
    <property name="jpaDialect">
        <bean class="cu.jpa.specifications.IsolationSupportHibernateJpaDialect" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">${hdm2ddl.auto}</prop>
        </props>
    </property>
    <property value="/META-INF/persistence.xml" name="persistenceXmlLocation"/>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven/>
.
.
.

这是我的persistence.xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">

<persistence-unit name="NewPersistenceUnit">
    <class>cu.jpa.entities.PatternEntity</class>
    .
    .
    .
    <class>cu.jpa.entities.TraceRegEntity</class>
</persistence-unit>
</persistence>

测试摘录class:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/repositories.xml"})
public class ServiceImplUpdateDescriptionTest {
    .
    .
    .
    @Test()
    public void testUpdateDescription_does_not_modify_description_with_invalid_values() throws Exception{
    .
    .
    .
    }



}

Spring 只会回滚事务,如果它是未经检查的异常,如果异常是经过检查的异常,那么您必须将其添加到您的@Transactional 注释中。

@Transactional(rollbackFor = SIASFaultMessage.class)