GVKun编程网logo

spring 事务传播行为之使用 REQUIRES_NEW 不回滚(spring事物不回滚)

18

在本文中,您将会了解到关于spring事务传播行为之使用REQUIRES_NEW不回滚的新资讯,同时我们还将为您解释spring事物不回滚的相关在本文中,我们将带你探索spring事务传播行为之使用R

在本文中,您将会了解到关于spring 事务传播行为之使用 REQUIRES_NEW 不回滚的新资讯,同时我们还将为您解释spring事物不回滚的相关在本文中,我们将带你探索spring 事务传播行为之使用 REQUIRES_NEW 不回滚的奥秘,分析spring事物不回滚的特点,并给出一些关于java – Spring事务中REQUIRES_NEW和NESTED传播行为的差异、Spring 中事务传播行为、Spring 中声明式事务的注解 @Transactional 的参数的总结(REQUIRED 和 REQUIRES_NEW 的与主方法的回滚问题)、Spring 事务传播属性介绍 (一).required 和 reuqires_new的实用技巧。

本文目录一览:

spring 事务传播行为之使用 REQUIRES_NEW 不回滚(spring事物不回滚)

spring 事务传播行为之使用 REQUIRES_NEW 不回滚(spring事物不回滚)

最近写 spring 事务时用到 REQUIRES_NEW 遇到一些不回滚的问题,所以就记录一下。

场景 1: 在一个服务层里面方法 1 和方法 2 都加上事务,其中方法二设置上 propagation=Propagation.REQUIRES_NEW, 方法 1 调用方法 2 并且在执行完方法 2 后抛出一个异常,如下代码

 1 @Service
 2 public class BookServiceImpl implements BookService {
 3     
 4     @Autowired
 5     private JdbcTemplate jdbcTemplate;
 6     
 7     @Transactional(timeout=4)
 8     public void update() {
 9         // TODO Auto-generated method stub
10         //售卖  扣除库存数量
11         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
12         //入账的sql  将赚到的钱添加到account表中的balance
13         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
14         Object []params = {1,"Spring"};
15     
16         jdbcTemplate.update(sellSql, params);
17         
18         testUpdate();
19         
20         jdbcTemplate.update(addRmbSql, params);
21         
22         throw new RuntimeException("故意的一个异常");
23     }
24     @Transactional(propagation=Propagation.REQUIRES_NEW)
25     public void testUpdate() {
26         //这个业务没什么意义,只是用来测试REQUIRES_NEW的 当执行后SpringMVC这本书库存-1
27         String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 
28         Object []params = {1,"SpringMVC"};
29         jdbcTemplate.update(sql, params);
30         
31     }

 

三张表分别是对应 account 表,book 表,book_stock 表

1 private static  ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/*.xml");
2         
3     @Test
4     public void testREQUIRES_NEW() {
5         
6         BookService bean = ac.getBean(BookService.class);
7         
8         bean.update();
9     }

 

结果是无论是方法 1 还是方法 2 都回滚了,那么 REQUIRES_NEW 就不起作用了,为了探索原因我修改了一下代码

在第 5 行的地方打印出对象的类型是什么

1 @Test
2     public void testREQUIRES_NEW() {
3         
4         BookService bean = ac.getBean(BookService.class);
5         System.out.println("update的调用者:"+bean.getClass());
6         bean.update();
7     }

在第 11 行的地方打印对象类型

 1 @Transactional(timeout=4)
 2     public void update() {
 3         // TODO Auto-generated method stub
 4         //售卖
 5         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
 6         //入账的sql
 7         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
 8         Object []params = {1,"Spring"};
 9     
10         jdbcTemplate.update(sellSql, params);
11         System.out.println("testUpdate的调用者:"+this.getClass());
12         testUpdate();
13         
14         jdbcTemplate.update(addRmbSql, params);
15         
16         throw new RuntimeException("故意的一个异常");
17     }

运行结果是

显然调用 update 的对象是一个代理对象,调用 testUpdate 的对象不是一个代理对象,这就是为什么添加 REQUIRES_NEW 不起作用,想要让注解生效就要用代理对象的方法,不能用原生对象的.

解决方法:在配置文件中添加标签 <aop:aspectj-autoproxy   expose-proxy="true"></aop:aspectj-autoproxy > 将代理暴露出来,使 AopContext.currentProxy () 获取当前代理

将代码修改为

<!-- 开启事务注解 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
	<!-- 将代理暴露出来 -->
	<aop:aspectj-autoproxy   expose-proxy="true"></aop:aspectj-autoproxy>

  11 12 行将 this 替换为 ((BookService) AopContext.currentProxy ())

 1     @Transactional(timeout=4)
 2     public void update() {
 3         // TODO Auto-generated method stub
 4         //售卖
 5         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
 6         //入账的sql
 7         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
 8         Object []params = {1,"Spring"};
 9     
10         jdbcTemplate.update(sellSql, params);
11         System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
12         ((BookService)AopContext.currentProxy()).testUpdate();
13         
14         jdbcTemplate.update(addRmbSql, params);
15         
16         throw new RuntimeException("故意的一个异常");
17     }

运行结果

调用的对象变成代理对象了  那么结果可想而知第一个事务被挂起,第二个事务执行完提交了 然后异常触发,事务一回滚  SpringMVC 这本书库存 - 1, 其他的不变

我还看到过另一种解决方法  

在第 7 行加一个 BookService 类型的属性并且给个 set 方法,目的就是将代理对象传递过来...    看 26 27 行显然就是用代理对象去调用的方法   所以就解决问题了   不过还是用第一个方案好

 1 @Service
 2 public class BookServiceImpl implements BookService {
 3     
 4     @Autowired
 5     private JdbcTemplate jdbcTemplate;
 6     
 7     private BookService proxy;
 8     
 9     public void setProxy(BookService proxy) {
10         this.proxy = proxy;
11     }
12     
13     @Transactional(timeout=4)
14     public void update() {
15         // TODO Auto-generated method stub
16         //售卖
17         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
18         //入账的sql
19         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
20         Object []params = {1,"Spring"};
21     
22         jdbcTemplate.update(sellSql, params);
23     /*    System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
24         ((BookService)AopContext.currentProxy()).testUpdate();*/
25         
26         System.out.println(proxy.getClass());
27         proxy.testUpdate();
28         
29         jdbcTemplate.update(addRmbSql, params);
30         
31         throw new RuntimeException("故意的一个异常");
32     }

OK 这个问题解决那就下一个

场景 2: 在一个服务层里面方法 1 和方法 2 都加上事务,其中方法二设置上 propagation=Propagation.REQUIRES_NEW, 方法 1 调用方法 2 并且在执行方法 2 时抛出一个异常     没注意看是不是觉得两个场景是一样的,因为我是拷贝下来改的...   差别就是在哪里抛出异常  这次是在方法 2 里面抛出异常,我将代码还原至场景 1 的第一个解决方案,然后在方法 2 里面抛出异常 代码如下

 1 @Service
 2 public class BookServiceImpl implements BookService {
 3     
 4     @Autowired
 5     private JdbcTemplate jdbcTemplate;
 6     
 7     @Transactional(timeout=4)
 8     public void update() {
 9         // TODO Auto-generated method stub
10         //售卖
11         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
12         //入账的sql
13         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
14         Object []params = {1,"Spring"};
15     
16         jdbcTemplate.update(sellSql, params);
17         
18         System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
19         ((BookService)AopContext.currentProxy()).testUpdate();
20         
21         jdbcTemplate.update(addRmbSql, params);
22         
23     }
24     @Transactional(propagation=Propagation.REQUIRES_NEW)
25     public void testUpdate() {
26         //这个业务没什么意义,只是用来测试REQUIRES_NEW的
27         String sql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)"; 
28         Object []params = {1,"SpringMVC"};
29         jdbcTemplate.update(sql, params);
30         
31         throw new RuntimeException("在方法二里面抛出一个异常");
32     }

预期结果是 testUpdate 这个事务是要回滚的,update 这个方法的事务正常执行,所以数据库的变化是 balance 字段的钱要 + 60  Spring 这本书的库存 - 1, 但是结果是数据库完全没有变化

分析:在 testUpdate 方法内抛异常被 spring aop 捕获,捕获后异常又被抛出,那么异常抛出后,是不是 update 方法没有手动捕获,而是让 spring aop 自动捕获,所以在 update 方法内也捕获到了异常,因此都回滚了

这张图片的代码是我 debug 模式下  在 testUpdate 方法中执行到抛出异常的地方  再点 step over 跳到的地方   显然 spring aop 捕获到了异常后,再次抛出,这就是为什么 update 方法会捕获到异常

 

OK 问题很简单   解决方案也很简单   只需要手动捕获该异常,不让 spring aop 捕获就 OK 了

将 update 方法改为

 1 @Transactional(timeout=4)
 2     public void update() {
 3         // TODO Auto-generated method stub
 4         //售卖
 5         String sellSql = "UPDATE book_stock SET stock = stock - ? WHERE isbn = (SELECT isbn FROM book WHERE NAME = ?)";
 6         //入账的sql
 7         String addRmbSql = "UPDATE account SET balance = balance + ? * (SELECT price FROM book WHERE NAME = ?)";
 8         Object []params = {1,"Spring"};
 9     
10         jdbcTemplate.update(sellSql, params);
11         
12         try {
13             System.out.println("testUpdate的调用者:"+((BookService)AopContext.currentProxy()).getClass());
14             ((BookService)AopContext.currentProxy()).testUpdate();
15         } catch (RuntimeException e) {
16             // TODO Auto-generated catch block
17             System.out.println(e.getMessage());
18             e.printStackTrace();
19         }
20         
21         jdbcTemplate.update(addRmbSql, params);
22         
23     }

执行结果    update 执行成功   testUpdate 回滚

 总结:同一个 Service 不同事务的嵌套会出现调用的对象不是代理对象的问题,如果是多个不同 Service 的不同事务嵌套就没有这个问题。场景 2 的要记得手动捕获异常,不然全回滚了。至于为什么调用 testUpdate 方法的对象不是代理对象,可能还要看源码,懂的人可以在评论区分享一下。

如果有错误,请评论区指正

原文出处:https://www.cnblogs.com/whwei-blog/p/10708926.html

java – Spring事务中REQUIRES_NEW和NESTED传播行为的差异

java – Spring事务中REQUIRES_NEW和NESTED传播行为的差异

前言

首先:

它不是Differences between requires_new and nested propagation in Spring transactions的重复 – 我读了它,但我没有找到我的问题的答案

题:

在阅读了我提到的主题之后,我理解了物理交易数量中传播水平的主要区别:
2 db事务 – 对于外部和内部方法的REQUIRES_NEW
1 db transaction – 用于nesTED for outer和for inner方法.如果底层数据库不支持保存点,它将无法工作

但是看起来逻辑从我的观点来看是相同的.

如何理解在实践中使用哪个级别?任何用例了解它?方便的行为差异的例子?

附:
我想其他事务差异有一些可见性,因为不同的事务提交时间.

P.S.2

另外我认为有性能差异:

@Transactional
public void outer(){
    for(int i=0;i<100500;i++){
        inner();
    }   
}

@Transactional
public void inner(){
   //some logic
}

对于那种情况,nesTED会更好,因为1长的物理交易而不是100500 1

最佳答案
我看到的巨大差异:

在嵌套的情况下:

>如果外部事务回滚,则嵌套的tra也会回滚.
>可见性:如果db执行的MVCC很常见,

>嵌套的tra看到外部tra的先前更改.
>在提交外部后,将看到嵌套tra的更改已提交并对其他tra可见.

> performance:请注意,外部事务的工作集由内部事务扩展.因此更多的锁,MVCC的更多preimage-storage,更长的redo-log-entry.

在requires_new的情况下:

>如果回滚外部事务,则在外部tra的回滚情况下,不会回滚内部tra的更改.
>可见性:在MVCC的情况下,这是非常常见的,

>内部tra不会看到尚未提交的外部tra所做的更改.
>在提交此内部tra后,即使在提交外部tra之前,嵌套tra的更改也将立即呈现为对其他tras提交并可见.较少的锁,但由于许多提交更多的外部操作,重做锁中的记录更多.

在性能方面,如果其他因素不重要,您可以在交易规模和交易数量之间找到收支平衡点. i.m.O这个问题没有一般的答案,如果嵌套比requires_new快.

Spring 中事务传播行为

Spring 中事务传播行为

1. Spring 中七种事务传播行为

PROPAGATION (蔓延、传播、传输)

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认的事务传播行为
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。(一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。)
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。(外层事务抛出异常回滚,那么内层事务必须回滚,反之内层事务并不影响外层事务)

 

2. Spring 中七种事务定义

Spring 在 TransactionDefinition 接口中规定了 7 种类型的事务传播行为。事务传播行为是 Spring 框架独有的事务增强特性。这是 Spring 为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。

TransactionDefinition 接口定义

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;

    int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    @Nullable
    String getName();
}

Transactional 注解的定义

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
​
    @AliasFor("value")
    String transactionManager() default "";
​
    Propagation propagation() default Propagation.REQUIRED;
​
    Isolation isolation() default Isolation.DEFAULT;
​
    int timeout() default -1;
​
    boolean readOnly() default false;
​
    Class<? extends Throwable>[] rollbackFor() default {};
​
    String[] rollbackForClassName() default {};
​
    Class<? extends Throwable>[] noRollbackFor() default {};
​
    String[] noRollbackForClassName() default {};
}

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

用伪代码说明:

public void methodA(){

methodB();

//doSomething

}

@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}

代码中 methodA () 方法嵌套调用了 methodB () 方法,methodB () 的事务传播行为 @Transaction (Propagation=XXX) 设置决定。这里需要注意的是 methodA () 并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。这里 methodA () 是外围方法,并没有开启事务。

@Transactional 的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的

2.1 验证 Propagation.REQUIRED

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequired(UserEntity userEntity){
   userMapper.insertUser(userEntity);
}

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addRequiredException(UserEntity userEntity){
   userMapper.insertUser(userEntity);
   throw new RuntimeException();
}

2.1.1 场景一

此场景外围方法没有开启事务。

验证方法 1:

@Override
public void noTransactionExceptionRequiredRequired(){
    UserEntity user1=new UserEntity();
    user1.setName("张三001");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四001");
    userService.addRequired(user2);
    throw new RuntimeException();
}

验证方法 2:

@Override
public void noTransactionRequiredRequiredException(){
    UserEntity user1=new UserEntity();
    user1.setName("张三002");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四002");
    userService.addRequiredException(user2);
}

验证结果:

验证方法序号 数据库结果 结果分析
1 “张三”、“李四” 均插入。 外围方法未开启事务,插入 “张三”、“李四” 方法在自己的事务中独立运行,外围方法异常不影响内部插入 “张三”、“李四” 方法独立的事务。
2 “张三” 插入,“李四” 未插入。 外围方法没有事务,插入 “张三”、“李四” 方法都在自己的事务中独立运行,所以插入 “李四” 方法抛出异常只会回滚插入 “李四” 方法,插入 “张三” 方法不受影响。

2.1.2 场景二

此场景外围方法开启事务。

验证方法 1:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionExceptionRequiredRequired(){
    UserEntity user1=new UserEntity();
    user1.setName("张三003");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四003");
    userService.addRequired(user2);

    throw new RuntimeException();
}

验证方法 2:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiredException(){
    UserEntity user1=new UserEntity();
    user1.setName("张三004");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四004");
    userService.addRequiredException(user2);
}

验证方法 3:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiredExceptionTry(){
    UserEntity user1=new UserEntity();
    user1.setName("张三005");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四005");
    try {
        userService.addRequiredException(user2);
    } catch (Exception e) {
        System.out.println("方法回滚");
    }
}

验证结果:

验证方法序号 数据库结果 结果分析
1 “张三”、“李四” 均未插入。 外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚。
2 “张三”、“李四” 均未插入。 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,外围方法感知异常致使整体事务回滚。
3 “张三”、“李四” 均未插入。 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被 catch 不被外围方法感知,整个事务依然回滚。

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

 

2.2. 验证 Propagation.PROPAGATION_REQUIRES_NEW

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNew(UserEntity userEntity){
   userMapper.insertUser(userEntity);
}

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addRequiresNewException(UserEntity userEntity){
   userMapper.insertUser(userEntity);
   throw new RuntimeException();
}

2.2.1 场景一

此场景外围方法没有开启事务。

验证方法 1:

@Override
public void noTransactionExceptionRequiresNewRequiresNew(){
    UserEntity user1=new UserEntity();
    user1.setName("张三006");
    userService.addRequiresNew(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四006");
    userService.addRequiresNew(user2);

    throw new RuntimeException();
}

验证方法 2:

@Override
public void noTransactionRequiresNewRequiresNewException(){
    UserEntity user1=new UserEntity();
    user1.setName("张三007");
    userService.addRequiresNew(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四007");
    userService.addRequiresNewException(user2);
}

验证结果:

验证方法序号 数据库结果 结果分析
1 “张三” 插入,“李四” 插入。 外围方法没有事务,插入 “张三”、“李四” 方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。
2 “张三” 插入,“李四” 未插入 外围方法没有开启事务,插入 “张三” 方法和插入 “李四” 方法分别开启自己的事务,插入 “李四” 方法抛出异常回滚,其他事务不受影响。

2.2.2 场景 2

外围方法开启事务。

验证方法 1:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionExceptionRequiredRequiresNewRequiresNew(){
    UserEntity user1=new UserEntity();
    user1.setName("张三008");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四008");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五008");
    userService.addRequiresNew(user3);
    throw new RuntimeException();
}

验证方法 2:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiresNewRequiresNewException(){
    UserEntity user1=new UserEntity();
    user1.setName("张三009");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四009");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五009");
    userService.addRequiresNewException(user3);
}

验证方法 3:

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void transactionRequiredRequiresNewRequiresNewExceptionTry(){
    UserEntity user1=new UserEntity();
    user1.setName("张三010");
    userService.addRequired(user1);

    UserEntity user2=new UserEntity();
    user2.setName("李四010");
    userService.addRequiresNew(user2);

    UserEntity user3=new UserEntity();
    user3.setName("王五010");
    try {
        userService.addRequiresNewException(user3);
    } catch (Exception e) {
        System.out.println("回滚");
    }
}

验证结果:

验证方法序号 数据库结果 结果分析
1 “张三” 未插入,“李四” 插入,“王五” 插入。 外围方法开启事务,插入 “张三” 方法和外围方法一个事务,插入 “李四” 方法、插入 “王五” 方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入 “张三” 的方法回滚。
2 “张三” 未插入,“李四” 插入,“王五” 未插入。 外围方法开启事务,插入 “张三” 方法和外围方法一个事务,插入 “李四” 方法、插入 “王五” 方法分别在独立的新建事务中。插入 “王五” 方法抛出异常,首先插入 “王五” 方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入 “张三” 方法也被回滚。
3 “张三” 插入,“李四” 插入,“王五” 未插入。 外围方法开启事务,插入 “张三” 方法和外围方法一个事务,插入 “李四” 方法、插入 “王五” 方法分别在独立的新建事务中。插入 “王五” 方法抛出异常,首先插入 “王五” 方法的事务被回滚,异常被 catch 不会被外围方法感知,外围方法事务不回滚,故插入 “张三” 方法插入成功。

结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

2.3 REQUIRED,REQUIRES_NEW,NESTED 异同

NESTED 和 REQUIRED 修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是 REQUIRED 是加入外围方法事务,所以和外围事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外围方法事务也将被回滚。而 NESTED 是外围方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚,不会影响到外围方法的事务。

NESTED 和 REQUIRES_NEW 都可以做到内部方法事务回滚而不影响外围方法事务。但是因为 NESTED 是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而 REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

2.4 PROPAGATION_MANDATORY,PROPAGATION_NEVER

2.4.1 PROPAGATION_MANDATORY 必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。

使用当前的事务,如果当前没有事务,就抛出异常。

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ''mandatory''

2.4.2 PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ''never''

2.5 PROPAGATION_SUPPORTS

如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。

Should roll back transaction but cannot - no transaction available

2.6 PAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

Should roll back transaction but cannot - no transaction available

Resuming suspended transaction after completion of inner transaction

3 使用场景

3.1 如果业务需求每接受到一次请求到要记录日志,如下图:

因为 insertOprLog () 的操作不管扣款和创建订单成功与否都要生成日志,并且日志的操作成功与否不影响充值处理,所以 insertOprLog () 方法的事务传播行为可以定义为:PROPAGATION_REQUIRES_NEW。

3.2 在银行新增银行卡业务中,需要执行两个操作,一个是保存银行卡信息,一个是激活新创建的银行卡,其中激活银行卡成功与否不影响银行卡的创建。

由以上需求,我们可知对于 cardActive () 方法的事务传播行为,可以设置为 PROPAGATION_NESTED,insertBankCardAndActive () 事务的回滚,cardActive () 激活的银行卡就没意义,也就需要跟着回滚,而 cardActive () 的回滚不影响 insertBankCard () 事务;insertBankCard () 的事务传播行为可以设置为 PROPAGATION_REQUIRED。

 

3.3 银行卡新增成功后,发送邮件给用户,发送邮件不涉及数据库的操作。sendCardMail () 可以设置为 PROPAGATION_NOT_SUPPORTED, 发送邮件不属于并且不应当影响主体业务逻辑,即使发送失败也不应该对主体业务逻辑回滚。

4 代码解析

TransactionInterceptor.invoke()

TransactionAspectSupport.invokeWithinTransaction()

AbstractPlatformTransactionManager.getTransaction(TransactionDefinition definition)

DataSourceTransactionManager

 

事务控制是通过 TransactionInterceptor.invoke () 方法来实现的。下面我们来看一下这个方法的大体结构

public Object invoke(MethodInvocation invocation) throws Throwable {
    // 获取被代理类
    Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
    Method var10001 = invocation.getMethod();
    invocation.getClass();
    // 执行事务方法
    return this.invokeWithinTransaction(var10001, targetClass, invocation::proceed);
}
    TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    result = null;
​
    try {
        result = invocation.proceedWithInvocation();
    } catch (Throwable var17) {
        this.completeTransactionAfterThrowing(txInfo, var17);
        throw var17;
    } finally {
        this.cleanupTransactionInfo(txInfo);
    }
​
    this.commitTransactionAfterReturning(txInfo);
    return result;
    }

 

 相关代码地址:https://gitee.com/weixiaotao1992/Working/tree/master/technology_code/propagation

Spring 中声明式事务的注解 @Transactional 的参数的总结(REQUIRED 和 REQUIRES_NEW 的与主方法的回滚问题)

Spring 中声明式事务的注解 @Transactional 的参数的总结(REQUIRED 和 REQUIRES_NEW 的与主方法的回滚问题)

一、事务的传播行为
1. 介绍

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
2. 属性

事务的传播行为可以由传播属性指定。Spring 定义了 7 种类传播行为。


系统默认的是 REQUIRED 属性。
常用的是 REQUIRED 和 REQUIRES_NEW, 所以此处只说明这两种属性。
下面先看系统默认的 REQUIRED 属性。


purchase 代表两个声明了事务的方法,并且传播行为是系统的默认行为。同时 checkout 也是一个声明了事务的方法,在该方法中调用前述的两个方法。当 checkout 执行到第一个方法的时候,第一个方法继续使用 checkout 的事务进行执行,第二个方法一样,所以整个方法只有一个事务。

下面介绍 REQUIRES_NEW 属性。

方法含义和上述一样,知识两个子方法的传播属性均为 REQUIRES_NEW。主方法的事务 tx1 执行到第一个方法的时候,挂起,然后子方法的事务进行,第二个方法类似。


如果一个事务发生了错误,那么回滚。所以 REQUIRED 属性中,如果第二个方法发生错误,第一个方法也会回滚,然而 REQUIRES_NEW 属性中,第二个方法发生错误,因为第一个是单独的事务,所以不会受到影响。

那么,如果两个混合使用呢?
(为简单起见,REQUIRED 在下述表达称为系统默认,REQUIRES_NEW 称为 new)
现在测试第一种方法的属性为系统默认,第二种方法为 new,第二种方法出现错误。此时结果是方法 1 也回滚。但是按照前面的理解,方法 2 是单独的事务,应该只造成自己回滚,为什么第一种方法也会回滚?

第一种方法发生错误后,产生错误造成本身回滚,但是他的异常因为没有捕获,所以传到了主方法的事务中,主方法的事务出现错误,所以回滚,第一个方法在主方法的事务中,所以第一个方法的 SQL 语句会回滚!
下面用一个简图总结一下:


如果第一种方法为 new, 第二种方法为系统默认,那么第二种发生错误后,主方法的事务回滚,然后第一种方法是自己的事务,所以不受影响,不回回滚,第一个方法的 SQL 语句就会执行。道理雷同,就不再画图表示。

Spring 事务传播属性介绍 (一).required 和 reuqires_new

Spring 事务传播属性介绍 (一).required 和 reuqires_new

Mandatory、Never、Not_Support 传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10260030.html

  Nested 传播属性分析传送门:https://www.cnblogs.com/lvbinbin2yujie/p/10260066.html

  我的 Spring 事务传播属性介绍比较传送门:https://files.cnblogs.com/files/lvbinbin2yujie/Spring_Tx_Note.rar

 

 

最近查看了 Spring 事务源码,是 4.2.x 的版本还是 4.3.x 的版本,简单了解了一些事务的概念,介绍下我对 Spring 事务源码的分析.

Spring 一共七种事务传播属性,本文先来作为开篇介绍。

  REQUIRED 事务,Spring Transactional 注解默认的事务,需要该方法在有事务情况下运行,如果当前没有事务就新建一个事物;

  REQUIRES_NEW 事务,当前方法运行没有事务,新建一个事物,当前方法有事务将当前事务挂起,新事物执行完毕再恢复原有事务;

这里提一点.,以前不明白什么是同一事务核心是什么,后来看到有位仁兄介绍,同一事务的事务信息、事务状态对象不同,但是底层是同一个 Connection 对象;这点我深以为.

 

实验说明环节

包结构:          

 

Spring 配置文件:  (简单介绍下,定义了一个数据源、DataSourceTransactionManager;此外 tx:annotation-driven 作用是用来启用 Transactional 注解的)

     

ServiceA.java 文件

 

ServiceB.java 文件

 

测试 Main 方法:

 

 

PROPAGATION_REQUIRED

说明: 默认的事务级别,需要事务;如果当前没有事务,则创建新的事务;如果有事务呢,就加入当前的事务;

如果所有的 Transactional 标签都是默认的,REQUIRED 时,方法里的提交、回滚都是一起的,要么所有都提交,要么所有都回滚;一荣俱荣,一损俱损;

 

查看源码时候打印事务的日志:

 

查看输出结果:  可以看到 确实提交了,并且加入了之前的事务,加入之前事务就是共用的一个 Connection 对象;

 

 情景二:  修改 ServiceB 的 addUser 方法  来模拟调用其他业务方法时候执行抛出运行时异常;  (同时将数据库之前测试结果 删除,便于观察)

 

   先查看结果:  艾尼路记录添加进来又回退了, 查看日志:

 

 

说明:  addUser 抛出类型为 RuntimeException 类型的异常,callB 捕获该异常之后,发现 addUser 在事物 callB 中,于是将事务状态 DefaultTransactionStatus 对戏中的事务对象 DataSourceTransactionObject 中的 ConnectionHolder 对象,即持有底层 Connection 的对象,将 ConnectionHolder 标记为 rollbackOnly 为 true,然后将异常抛给调用 addUser 的 callB 方法;

在 callB 调用时根据 Spring 事务回滚规则,决定回滚操作;因为在情景二同一个事务中 ConnectionHolder 中持有的 Connection 对象是同一个,所以 callB 方法整个回滚了;

 

 

情景三: callB 方法中手动 try-catch 来捕获异常会发生什么呢?

         修改 ServiceA 的 callB 方法 (为了便于观察,不输出异常,只是简单打印)

 

直接查看输出日志 (最后一行抛出的异常排版原因未截全): 查看日志,记录确实添加了但是又回滚过了,数据库里没有记录。

异常信息:

Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

 

 

说明: callB 作为事务的开始,addUser 作为事务的一部分,addUser 抛出异常以后将 ConnectionHolder 对象的 rollbackOnly 设置为 true,标记为需要回滚,但是执行完 callB 方法,没有抛出异常,就认为应该正常提交; 提交之前会校验两次,有一次是 Global transaction is marked as rollback-only but transactional code requested commit;

意思是全局事务需要回滚操作,但是事务代码现在要提交,这时候 Spring 还是会回滚;(通俗点就是 addUser 方法需要回滚,但是 callB 方法没有发生预料之外的异常,因为异常自己手动捕获,这时候 Spring 还是会回滚);

         callB 和 ServiceB 的事务信息对象 TransactionInfo 是不一样的,其属性事务状态 TransactionStatus 也不是同一个对象,TransactionStatus 的底层事务对象 transaction 也不是同一个对象,transaction 持有 ConnectionHolder 对象的 Connection 确实同一个,这样就保证了可以使用 Connection 对象来保持事务的一致性,一起提交、一起回滚; 保证两个 Connection 是同一个对象的是 ThreadLocal,TransactionSynchronizationManager 的 resources 里存放了着;

 

总结:  REQUIRED 不建议手动捕获异常,会破坏 Spring 的事务规则;try-catch 需要结合传播类型,再决定使用与否;

 

PROPAGATION_REQUIRES_NEW

说明: 创建新的事务并执行,如果当前有事务,那将当前事务挂起,新建一个事物;

 

给 ServiceA 类 callB 方法修改下:

ServiceB 类添加方法 addUserFail, 事务属性设置为 REQUIRE_NEW

 

正常情况下查看输出日志:

可以发现,当进入 REQUIRED_NEW 事务里的方法时,挂起了原来的事务,事务执行完毕恢复了事务;并且外层事务和 addUserFail 事务是分别提交的;

 

 

情景三.

ServiceA 方法:

 

ServiceB 方法:

测试类方法:

 

 

说明:callB2 方法调用了 ServiceB 的两个事务方法,ddUserSuccess 方法是 REQUIRED 事务,addUserFail 方法是 REQUIRED_NEW 事务,按照之前分析的,REQUIRED_NEW 的方法是一个新的事务,那我抛出异常自己就会回滚,不应该干扰到 callB2 方法的回滚;

执行结果: addUserSuccess 方法被带着一起回滚了,即外层事务也被带着一起回滚了;

 

查看源码发现: addUserFail 的确新建了事务,然后抛出异常之后,着手回滚,回滚完成后将异常 throw 了,异常被 throw 那就会丢给调用 addUserFail 的地方,没错,丢到了 callB2 方法里,那 callB2 也着火了,发生异常,这时候 callB2 的事务也被认定为执行失败应当回滚,那 callB2 的事务就开始回滚,callB2 内部事务 addUserSuccess 回滚, 所以一条记录都没有写进去.

(想了下,REQUIRED_NEW 事务的方法就在调用他的地方手动捕获异常,不让异常向上传递了,这样就能达到目的,且不会像 REQUIRED 一样报异常)

 

简单画了如下例子,外层事务为 REQUIRED 类型;外层事务有两个方法,A、B;Spring 默认回滚规则为 RuntimeException 或 Error 类型,下面例子抛出异常也是 RumtimeException 或 Error 类型,且没有手动 try-catch 捕获异常;

 

 

我们今天的关于spring 事务传播行为之使用 REQUIRES_NEW 不回滚spring事物不回滚的分享已经告一段落,感谢您的关注,如果您想了解更多关于java – Spring事务中REQUIRES_NEW和NESTED传播行为的差异、Spring 中事务传播行为、Spring 中声明式事务的注解 @Transactional 的参数的总结(REQUIRED 和 REQUIRES_NEW 的与主方法的回滚问题)、Spring 事务传播属性介绍 (一).required 和 reuqires_new的相关信息,请在本站查询。

本文标签: