如何不回滚以前的方法持久化数据?

Posted

技术标签:

【中文标题】如何不回滚以前的方法持久化数据?【英文标题】:How not to roll back previous methods persist data? 【发布时间】:2014-10-20 13:37:11 【问题描述】:

我的 DAO 界面很简单:

import org.springframework.transaction.annotation.Transactional;

@Component
@Transactional
public interface TTestDao 

    @Transactional()
    public void first();

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void second() ;


ad impl 方法:

@Override
public void first() 
          entityManager.persist(new TableTest1().setName("data1"));
          this.second();      


public void second() 
     entityManager.persist(new TableTest1().setName("data2"));
     throw new RuntimeException(); // it roll backs data2 and data1

第一种方法调用第二种方法。第二种方法出现错误。

目前,如果我调用first(),所有持久化的信息都将被回滚。但是为什么会这样呢? second() 方法在新事务中,我需要将第一个方法的数据保留在数据库中。

换句话说,我需要始终保留第一个方法的数据,但只回滚第二个方法的数据。我想一直写data1。

我有什么问题吗?

我在 SPRING 中有这样一个 db 配置:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@127.0.0.1:1521:xe"
    p:username="dev" p:password="****" >
</bean>


<bean id="entityManagerFactory" 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">
        <props>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
        </props>
    </property>
</bean>

<context:component-scan base-package="ge.ddrc.transport.persistance.entity">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
</context:component-scan>


<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />

我的应用程序在 tomcat 中运行(如果有任何意义)

【问题讨论】:

【参考方案1】:

@StijnGeukens 很接近。 Spring 实际上确实使用代理来处理事务,default 从另一个事务方法中调用一个事务方法不会导致创建新事务。但是,在这种情况下,传播被明确设置为显式 require a new 创建事务。应该创建 2 个事务:

T1: create
    T2: create
    Exception occurs here
    T2: commit
T1: commit

如您所见,异常发生在任一事务到达其提交点之前。因为它不在First 中处理,所以执行永远不会到达T1 的提交点。这将导致***事务被回滚。

但是,您可以处理 First 中的异常,但它仍然无法正常工作。从我上面链接到的文档中:

Note: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to JtaTransactionManager, which requires the javax.transaction.TransactionManager to be made available it to it (which is server-specific in standard J2EE).

所以您的propagation=REQUIRES_NEW 可能实际上并没有按照它所说的那样做,因为它不喜欢您的TransactionManager。欢迎来到春天。

更新 来自JpaTransactionManager 文档:

在 JDBC 3.0 上,此事务管理器通过以下方式支持嵌套事务 JDBC 3.0 保存点。这 不过,AbstractPlatformTransactionManager.setNestedTransactionAllowed(boolean) "nestedTransactionAllowed" 标志默认为“false”,因为 嵌套事务只适用于 JDBC 连接,而不适用于 JPA EntityManager 及其缓存对象。您可以手动设置 如果要使用嵌套事务进行 JDBC 访问,请标记为“true” 参与 JPA 事务的代码(前提是您的 JDBC 驱动程序支持保存点)。注意JPA本身不支持 嵌套事务!因此,不要期望 JPA 访问代码 语义上参与嵌套事务。

这非常令人困惑,但我认为它的意思是,如果您设置提到的标志,您将能够使用嵌套事务,使用 JDBC 3.0 的功能实现。因此,请确保您的驱动程序符合该规范。

【讨论】:

感谢您的出色回答!我会给你+1票!我已经更新了我的问题。据我了解,我应该使用 JTA 事务管理器来解决我的问题。是真的吗? p.s 什么是最佳实践,春季哪个事务管理器最好?如何选择?我也在数据库配置中使用aspectj,这也是一种好习惯吗?我是春天的新人。再次感谢! @grep 查看我的更新。只要您设置了适当的标志,您似乎仍然可以使用相同的事务管理器。就您的tx:annotation-driven mode 而言,根据this,ASPECTJ 似乎是合适的选择。 目前我在 XML 配置中使用 JpaTransactionManager。在 java 中,我使用 EntityManager 来处理 db。我应该在哪里声明 AbstractPlatformTransactionManager?我应该将 EntityManager 更改为 AbstractPlatformTransaction Manager 吗?我无法在网络中找到样本...【参考方案2】:

第二种方法仍然抛出异常。所以,是的,您创建了 2 个事务,但由于相同的异常,两个事务都将被回滚。如果您不想回滚第一个方法,那么您必须用 try catch 包围对第二个方法的调用(这样不会向 Transactional 切入点抛出异常)。

一般来说,从另一个事务调用一个事务并不是一个好主意,因为不清楚事务的范围是什么(这在界面上不可见)。因此,如果可能的话,最好从前端(或任何地方)分别调用这两种方法,并忽略那里第二种方法的任何异常。

更新

然而,你的问题是完全不同的。 Spring 事务使用处理事务(和异常)的代理。由于您从 first() 调用 second(),因此您完全绕过了代理,因此永远不会创建新事务。

【讨论】:

try this.second(); catch () - 当我这样做时,它会将所有方法数据 - data1 和 data2 保存在表中。我只想保留 data1。 试试这个更现实的用例:使数据库列名不可为空,然后在持久化 TableTest1 时不要设置名称。还要考虑我帖子中的第二段。 try catch 不能解决问题。这是不正确的。 我的回答(和 jmvivo 的回答)是正确的,但您的问题有不同的原因(请参阅我的更新)。 谢谢你 Stijn。你想写“从未创造过”而不是“曾经创造过”。如果 spring 将创建新事务,一切都会好起来的不是吗?...有什么 spring 解决方案吗?...【参考方案3】:

尝试在first 上捕获second 异常:

@Override
public void first() 
      entityManager.persist(new TableTest1().setName("data1"));
      try 
         this.second();      
       catch (RuntimeException e) 
        // Do nothing
      


public void second() 
      entityManager.persist(new TableTest1().setName("data2"));
     throw new RuntimeException(); // it roll backs data2

【讨论】:

没有。它颂歌不回滚“data2”。我只想保存 first() 方法数据。但它会保存所有方法数据。 try this.second(); catch () - 当我这样做时,它会将所有方法数据 - data1 和 data2 保存在表中。我只想保留data1。

以上是关于如何不回滚以前的方法持久化数据?的主要内容,如果未能解决你的问题,请参考以下文章

Junit测试在事务后不回滚

在集成测试中每个方法不回滚后 Spring Data JPA 数据库更改

spring配置了事务,抛出异常不回滚

面试突击86:SpringBoot 事务不回滚?怎么解决?

java jdbc addBatch批处理不回滚

java中事务不回滚!