SpringBoot事务一点点分享

444 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

SpringBoot事务操作

事务操作与实现

编程式事务

在 Spring Boot 中实现编程式事务有两种实现方法:

  • 使用 TransactionTemplate 对象实现编程式事务;
  • 使用更加底层的 TransactionManager 对象实现编程式事务。

TransactionTemplate使用

在当前类注入 TransactionTemplate ,再用它提供的 execute 方法执行事务并返回相应的执行结果,如果程序在执行途中出现异常,可以使用代码手动回滚事务

   private    TransactionTemplate transactionTemplate;
  
  Integer execute = transactionTemplate.execute(status -> {
                    int result = 0;
                    try {
                        result = result + 1;
                    } catch (Exception e) {
                        //手动回滚
                        status.setRollbackOnly();
                    }
                    return result;
                }
        );

TransactionManager 使用

需要使用两个对象:TransactionManager 的子类,加上 TransactionDefinition 事务定义对象,再通过调用 TransactionManager 的 getTransaction 获取并开启事务,然后调用 TransactionManager 提供的 commit 方法提交事务,或使用它的另一个方法 rollback 回滚事务。

    private TransactionDefinition transactionDefinition;
    
    private DataSourceTransactionManager transactionManager;
    
        //开启事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        //业务操作
        int i = 0;
        //提交事务
        transactionManager.commit(transaction);
        //回滚
        transactionManager.rollback(transaction);

声明式事务

声明式事务的实现比较简单,只需要在方法上或类上添加 @Transactional 注解即可,当加入了 @Transactional 注解就可以实现在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。

注解的参数

image.png

事务隔离级别

简介

事务隔离级别是对事务 4 大特性中隔离性的具体体现,使用事务隔离级别可以控制并发事务在同时执行时的某种行为

Spring事务隔离级别

DEFAULT:Spring默认的事务隔离级别,一连接的数据库的事务隔离级别为准;

READ_UNCOMMITTED:读未提交。该级别的事务可以看到其他事务未提交的数据。但是会发生脏读,一个事务读到另一个事务未提交的数据,未提交的数据会发生回滚。

READ_COMMITTED:读已提交。该隔离级别的事务能读取到已经提交事务的数据。

REPEATABLE_READ:可重复读,它能确保同一事务多次查询的结果一致,但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为查询结果一样,所以查询不到这条数据,自己插入是会报错,这就是幻读

SERIALIZABLE:串行化,最高的事务隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

脏读:一个事务读取到了另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。

不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。

幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务有新增了一部分数据。

设置事务隔离级别

事务的隔离级别有2种设置方式

编程式事务

@Resource
private TransactionTemplate transactionTemplate;
​
​
//设置为默认
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);

声明式事务

@Transactional(isolation = Isolation.DEFAULT)

注意

Spring 的事务隔离级别是建立在连接的数据库支持事务的基础上的,如果 Spring 项目连接的数据库不支持事务(或事务隔离级别),那么即使在Spring中设置了事务隔离级别,也是无效的设置。

事务失效的原因和解决办法

@Transactional 执行流程是: @Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。

@Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的。

导致@Transactional 失效的常见场景有以下5个:

非 public 修饰的方法

原因

@Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的,而 @Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象,这样也就没办法自动执行事务了

解决方法

将方法的权限修饰符改为 public 即可。

try/catch 导致事务失效

原因

当执行的方法中出现了异常,@Transactional 才能感知到,然后再执行事务回滚,而当开发者自行添加了 try/catch 之后,@Transactional 就感知不到异常了,从而就不会触发事务的自动回滚了

解决方法

1.将异常重新抛出:在catch部分代码 throw 错误

2.使用代码手动回滚事务:在catch 部分代码 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

调用类内用的 @Transactional 方法

原因

调用类内部的方法时,不是通过代理对象完成的,而是通过 this 对象实现的,这样就绕过了代理对象,从而事务就失效了。

解决方法

在调用的方法上也加上 @Transactional,这样被调用的方法和调用方法都有事务。

其他解决办法

检查异常的事务解决

@Transactional 默认只回滚运行时异常 RuntimeExceptionError,而对于检查异常默认是不回滚的

@Transactional 注解上,添加 rollbackFor 参数并设置 Exception.class 值即可

数据库不支持事务的解决方案

设置MySQL的引擎为InnoDB,InnoDB支持事务。

事务的传播机制

包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。

比如方法 A 开启了事务,而在执行过程中又调用了开启事务的 B 方法,那么 B 方法的事务是应该加入到 A 事务当中呢?还是两个事务相互执行互不影响,又或者是将 B 事务嵌套到 A 事务中执行呢?所以这个时候就需要一个机制来规定和约束这两个事务的行为,这就是 Spring 事务传播机制所解决的问题。

Spring 事务传播机制可使用 @Transactional( propagation = Propagation.REQUIRED ) 来定义,

Spring 事务传播机制的级别包含以下 7 种 3 大类:

支持当前事务

共同点:如果当前存在事务,则加入该事务

REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务

SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

MANDATORY:果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

不支持当前事务

共同点:都与调用方法的事务无关,要么挂起,要么抛异常

REQUIRES_NEW:表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰

NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起

NEVER:以非事务方式运行,如果当前存在事务,则抛出异常

嵌套事务

NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED