• spring事务管理


    转载:https://mp.weixin.qq.com/s/11GR1TuGQgqsj83jSRraRw

    1. 关键类

    1 public interface PlatformTransactionManager {
    2     TransactionStatus getTransaction(
    3             TransactionDefinition definition) throws TransactionException;
    4     void commit(TransactionStatus status) throws TransactionException;
    5     void rollback(TransactionStatus status) throws TransactionException;
    6 }
    View Code

    事务真正的开始、提交、回滚都是通过PlatformTransactionManager这个接口来实现的,例如,我们常用的org.springframework.jdbc.datasource.DataSourceTransactionManager。

    TransactionDefinition用于获取事务的一些属性,Isolation, Propagation,Timeout,Read-only,还定义了事务隔离级别,传播属性等常量。 

    TransactionStatus用于设置和查询事务的状态,如是否是新事务,是否有保存点,设置和查询RollbackOnly等。

    2. 声明式事务

    所谓声明式事务,就是通过配置的方式省去很多代码,从而让Spring来帮你管理事务。本质上就是配置一个Around方式的AOP,在执行方法之前,用TransactionInterceptor截取,然后调用PlatformTransactionManager的某个实现做一些事务开始前的事情,然后在方法执行后,调用PlatformTransactionManager的某个实现做commit或rollback。

    3. 事务属性

    • value,在有多个事务管理器存在的情况下,用于标识使用哪个事务管理器

    • isolation,事务的隔离级别,默认是Isolation.DEFAULT,这个DEFAULT是和具体使用的数据库相关的。关于隔离级别,可以参考MySQL事务学习总结

    • readOnly, 是否只读,如果配置了true,但是方法里使用了update,insert语句,会报错。对于只读的事务,配置为true有助于提高性能。

    • rollbackFor, noRollbackFor. Spring的声明式事务的默认行为是如果方法抛出RuntimeException或者Error,则事务会回滚,对于其他的checked类型的异常,不会回滚。如果想改变这种默认行为,可以通过这几个属性来配置。

    • propagation, 事务的传播机制。

    4. 事务的传播机制

    类型说明
    PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。
    PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
    PROPAGATION_MANDATOR 使用当前的事务,如果当前没有事务,就抛出异常
    PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起
    PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
    PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作

    PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。

     1 @Service
     2 public class MysqlTest01 {
     3     @Autowired
     4     private JdbcTemplate jdbcTemplate;
     5     @Autowired
     6     private MysqlTest02 mysqlTest02;
     7 
     8     @Transactional
     9     public void test() {
    10         jdbcTemplate.execute("update test set money = '501' where id = 3");
    11         try {
    12             mysqlTest02.test();
    13         } catch (Exception e) {
    14             System.out.println("第二个事务异常");
    15         }
    16     }
    17 }
    18 
    19 @Service
    20 class MysqlTest02 {
    21     @Autowired
    22     private JdbcTemplate jdbcTemplate;
    23 
    24     @Transactional(propagation = Propagation.REQUIRED)
    25     public void test() {
    26         jdbcTemplate.execute("update test set money = '502' where id = 3");
    27         throw new RuntimeException();
    28     }
    29 }
    View Code

    执行完之后,test表的数据没有任何变化。

    由于MysqlTest02中的事务传播类型是Propagation.REQUIRED,逻辑上有两个事务,但底层是共用一个物理事务的,第二个事务的抛出RuntimeExcetion导致事务回滚,对于这种传播类型,内层事务的回滚会导致外层事务回滚。所以数据库中的数据没有任何变化。

    PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起

     1 @Service
     2 public class MysqlTest01 {
     3     @Autowired
     4     private JdbcTemplate jdbcTemplate;
     5     @Autowired
     6     private MysqlTest02 mysqlTest02;
     7 
     8     @Transactional
     9     public void test() {
    10         jdbcTemplate.execute("update test set money = '501' where id = 3");
    11         try {
    12             mysqlTest02.test();
    13         } catch (Exception e) {
    14             System.out.println("第二个事务异常");
    15         }
    16     }
    17 }
    18 
    19 @Service
    20 class MysqlTest02 {
    21     @Autowired
    22     private JdbcTemplate jdbcTemplate;
    23 
    24     @Transactional(propagation = Propagation.REQUIRES_NEW)
    25     public void test() {
    26         jdbcTemplate.execute("update test set money = '502' where id = 3");
    27         throw new RuntimeException();
    28     }
    29 }
    View Code

    同样的代码,唯一的区别就是第二个事务的传播属性改成了REQUIRES_NEW,执行结果是啥?不好意思,第二个事务执行不了。

    对于REQUIRES_NEW,逻辑上有两个事务,底层物理上也有两个事务,由于第一个事务和第二个事务更新的是同一条记录,对于Mysql默认的隔离级别REPEATABLE-READ来说,第一个事务会对该记录加排他锁,所以第二个事务就一直卡住了。

    OK,我们把第二个事务的执行的SQL语句换成。

    update test set money = '501' where id = 5"

    执行结果如下,可以看到只有第二个事务回滚了。

     PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作

    对于这种传播类型,物理上只有一个事务,不过可以有多个savePoint用来回滚。当然是用这种传播类型,需要数据库支持savePoint,使用jdbc的也是要3.0版本以上(这个不太确定)。

     1 @Service
     2 public class MysqlTest01 {
     3     @Autowired
     4     private JdbcTemplate jdbcTemplate;
     5     @Autowired
     6     private MysqlTest02 mysqlTest02;
     7     @Autowired
     8     private MysqlTest03 mysqlTest03;
     9 
    10     @Transactional
    11     public void test() {
    12         jdbcTemplate.execute("update test set money = '501' where id = 3");
    13         try {
    14             mysqlTest02.test();
    15         } catch (Exception e) {
    16             System.out.println("第二个事务异常");
    17         }
    18         mysqlTest03.test();
    19     }
    20 }
    21 
    22 @Service
    23 class MysqlTest02 {
    24     @Autowired
    25     private JdbcTemplate jdbcTemplate;
    26 
    27     @Transactional(propagation = Propagation.NESTED)
    28     public void test() {
    29         jdbcTemplate.execute("update test set money = '502' where id = 3");
    30         throw new RuntimeException();
    31     }
    32 }
    33 
    34 @Service
    35 class MysqlTest03 {
    36     @Autowired
    37     private JdbcTemplate jdbcTemplate;
    38 
    39     @Transactional(propagation = Propagation.NESTED)
    40     public void test() {
    41         jdbcTemplate.execute("update test set money = '503' where id = 3");
    42     }
    43 }
    View Code

    执行结果是如下,可以看到第一个事务和第三个事务提交成功了,第二个事务回滚了。物理上它们是在一个事务里的,只不过用到了保存点的技术。

    其他

    在写测试代码的时候遇到了一个关于AOP的问题,可以看到我的测试代码,每个事务都是在一个新的class中写的。为什么不像下面这样写呢?

     1 @Service
     2 public class MysqlTest01 {
     3     @Autowired
     4     private JdbcTemplate jdbcTemplate;
     5 
     6     @Transactional
     7     public void test01() {
     8         jdbcTemplate.execute("update test set money = '501' where id = 3");
     9         test02();
    10     }
    11     @Transactional
    12     public void test02() {
    13         jdbcTemplate.execute("update test set money = '501' where id = 5");
    14     }
    15 }
    View Code

    这是因为在Spring的AOP中,test01调用test02, test02是不会被AOP截获的,所以也不会被Spring进行事务管理。原因是Spring AOP的实现本质是通过动态代理的方式去执行真正的方法,然后在代理类里面做一些额外的事情。当通过别的类调用MysqlTest01中的test01方法时,因为使用了Spring的DI,注入的其实是一个MysqlTest01的一个代理类,而通过内部方法调用test02时,则不是。

  • 相关阅读:
    LintCode 9.Fizz Buzz 问题(JAVA实现,一个if都不用)
    Docker中使用ElasticSearch
    Docker中使用RabbitMQ
    SpringBoot 缓存工作原理
    SpringBoot 启动配置原理
    docker 常用命令
    SpringBoot 自动配置原理
    动态规划求斐波那契数列
    MySQL 日期加减
    【Linux】Ubuntu:取消用户登录密码
  • 原文地址:https://www.cnblogs.com/cq-yangzhou/p/10996981.html
Copyright © 2020-2023  润新知