持续创作,加速成长!这是我参与「掘金日新计划 · 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 注解就可以实现在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。
注解的参数
事务隔离级别
简介
事务隔离级别是对事务 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 默认只回滚运行时异常 RuntimeException 和 Error,而对于检查异常默认是不回滚的
@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