1. 快速使用
- 事务支持
备注:使用事务的时候,一定要首先确保当前数据库的引擎是否支持事务,如果数据库引擎不支持事务,则任何配置都是徒劳的。
例如:MySQL 数据库 InnoDB 支持事务,而 MyISAM 不支持事务。
- 引入依赖
备注:已经引入了 mybatis-plus-boot-starter 则无需再次引入 spring-boot-starter-jdbc。
原因:mybatis-plus-boot-starter 内部已经引入了 spring-boot-starter-jdbc。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${jdbc.version}</version>
</dependency>
- 添加注解
备注:注解一般添加在自己的业务方法上面 (一般来说都是添加在 service 层的方法上面)
package com.dme.modules.service.transactional.impl;
/**
* @Author: 喜欢编程的代先生
* @Date: 2022-06-12 12:39
* @Description: 拒绝侵权
*/
@Service
public class TransactionServiceImpl extends ServiceImpl<TransactionDemoDao, TransactionOne> implements TransactionService {
private Logger logger = LoggerFactory.getLogger(TransactionServiceImpl.class);
private TransactionDemoDao transactionDemoDao;
@Autowired
public void setTransactionDemoDao(TransactionDemoDao transactionDemoDao) {
this.transactionDemoDao = transactionDemoDao;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void testDelete() {
logger.info("测试删除........");
int i = transactionDemoDao.deleteById("1539393513600253961");
logger.info("受影响行数 :" + i);
int a = 1 / 0;
}
}
2. 详细介绍
在 Spring 中,事务的实现方式有两种,分别是编程式事务和声明式事务管理两种方式。
- 编程式事务管理
编程式事务管理使用 TransactionTemplate或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring 推荐使用 TransactionTemplate。
- 声明式事务管理
声明式事务管理建立在 AOP 之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行目标方法之后根据执行的情况提交或者回滚事务。声明式事务管理不需要入侵代码,通过 @Transactional 就可以进行事务的操作,推荐使用。
1. 事务注解
备注:@Transactional 只能放在 public 修饰的方法上。默认情况下,该注解只对 RuntimeException 及其子类异常执行事务回滚。
参数名称功能描述readOnly该属性用于设置当前事务是否为只读模式,设置为 true 表示只读,false 表示可读可写,默认情况下是 false。
例如: @Transactional(readOnly = true)rollbackFor该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
例如:
指定单一异常类:@Transactional(rollbackFor = Exception.class)
指定多个异常类:@Transactional(rollbackFor = {RuntimeException.class,Exception.class})rollbackForClassName该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。
例如:
指定单一异常类名:@Transactional(rollbackForClassName = “RuntimeException”)
指定多个异常类名:@Transactional(rollbackForClassName = {“RuntimeException”,”Exception”})noRollbackFor该属性用于设置不需要进行回滚的异常类数组。使用方法同 rollbackFornoRollbackForClassName该属性用于设置不需要进行回滚的异常类名称数组。使用方法同 rollbackForClassNamepropagation该属性用于设置事务的传播行为。isolation该属性用于设置事务的隔离级别。timeout该属性用于设置事务的超时秒数,默认值为 -1 表示永不超时。
2. 隔离级别
- 隔离级别
数据库标准提出了四种事务隔离级别,分别为:读未提交、读已提交、可重复读、串行化。
- 枚举映射
package org.springframework.transaction.annotation;
public enum Isolation {
DEFAULT(-1), // springboot 默认隔离级别,可以通过配置文件进行配置
READ_UNCOMMITTED(1), // 读未提交
READ_COMMITTED(2), // 读已提交
REPEATABLE_READ(4), // 可重复读
SERIALIZABLE(8); // 串行化
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- 使用方法
@Transactional(isolation = Isolation.DEFAULT)
public void testAdd(){
logger.info("测试默认隔离级别......");
}
3. 传播行为
- 7 种传播行为
springboot 事务机制种对数据库存在七种传播行为,常用的传播行为主要有三种:REQUIRED、REQUIRES_NEW、NESTED
REQUIRED:需要事务,它是默认的传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。
SUPPORTS:支持事务,如果当前存在事务,就沿用当前事务,如果不存在事务,则继续采用无事务的方式运行子方法
MANDATORY:必须使用事务,如果当前没有事务,则会抛出异常,如果当前存在事务,就沿用当前的事务。
REQUIRES_NEW:无论当前事务是否存在,都会创建新的事务去运行子方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。
NOT_SUPPORTED:不支持事务,如果当前存在事务,则将事务挂起,运行子方法。
NEVER:不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制继续运行。
NESTED:当前方法调用子方法时,如果子方法发生异常,则回滚子方法执行过的 SQL,而不回滚当前方法的事务。
- 枚举映射
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- 使用方法
@Transactional(propagation = Propagation.NESTED)
public void testPropagation(){
logger.info("测试事务传播行为......");
}
4. 事务超时
事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制事务还未完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。默认设置为底层数据库事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是 none,即没有超时限制。
5. 回滚机制
- 自动回滚
备注:对可能出现异常的地方不要进行 try catch 操作。
@Transactional(rollbackFor = Exception.class)
public void testRollback(){
logger.info("测试异常自动回滚......");
}
- 手动回滚
备注:对可能出现异常的地方进行 try catch,并在 捕获异常的同时进行回滚的操作。
@Transactional(rollbackFor = Exception.class)
public void testRollback(){
logger.info("测试异常自动回滚......");
try {
int i = 1 / 0;
}catch (Exception e){
logger.error("异常信息:" + e.getMessage());
TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
transactionStatus.setRollbackOnly();
}
}
- 设置回滚点
备注:将当前操作回滚到设置的回滚点的位置。
@Transactional(rollbackFor = Exception.class)
public void testRollback(){
logger.info("业务逻辑一...........");
logger.info("业务逻辑二...........");
/**设置回滚点*/
Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
logger.info("业务逻辑三...........");
logger.info("测试异常自动回滚......");
try {
int i = 1 / 0;
}catch (Exception e){
logger.error("异常信息:" + e.getMessage());
TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
/**回滚到指定位置,也就是说,业务逻辑一和业务逻辑二的逻辑不会进行回滚*/
transactionStatus.rollbackToSavepoint(savepoint);
}
}
3. 常见问题
1. @EnableTransactionManagement
问题:springboot 项目中是否需要在 启动类上添加 @EnableTransactionManagement 注解?
答案:不需要。因为 springboot 自动装配已经帮助我们处理了,springboot 项目默认支持了事务。
2. @Transactional 失效
问题:方法上添加了 @Transactional 注解,为什么没有进行回滚?
排查一:是否使用了 try catch 进行了异常捕获,并且捕获之后,没有通过 throw new RuntimeException(); 进行异常抛出。因为对于 spring aop 异常捕获原理,被拦截的方法需要显示的抛出异常,并不能进行任何处理,这样 aop 代理才能捕获到方法的异常,才能进行事务的回滚操作;默认清空下,aop 只捕获 RuntimeException 的异常,但是可以通过配置来捕获特定的异常并回滚。
排查二:是否使用了 try catch 进行了异常信息捕获,如果是可以在 catch 语句中添加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
排查三:排查是不是产生了自调用的问题。在 spring 的 aop 代理下,只有目标方法被外部调用,目标方法才由 spring 生成的代理对象来管理;若统一类中的其他没有用 @Transactional 注解进行修饰的方法内部调用了 用 @Transactional 注解进行修饰的方法,有 @Transactional 注解的方法的事务将会被忽略,不发生回滚。
备注:如果确实需要这样操作,只需要把 @Transactional 注解加在当前的类名上就可以了 或者使用 AspectJ 取代 spring aop 进行代理。
排查四:@Transactional 注解只被应用到 public 修饰的方法上;如果在 protected、private等修饰的方法上,@Transactional 注解不会报错,但是这个注解的将不会生效。
排查五:@Transactional 注解不会对当前修饰的方法的子方法生效。比如:我们在方法 A 中声明了 @Transactional 注解,但是 A 方法的内部调用的 方法 B 和 方法 C,其中方法 B 进行了 数据库的操作,但是改部分的异常被方法 B 进行了处理并且没有进行 抛出,这样的话事务是不会生效的。反之,如果 方法 B 声明了 @Transactional,但是方法 A 没有声明 @Transactional,A 方法内部调用 B 方法,事务也是不会生效的。如果想要事务生效,需要将子方法的事务控制交给调用的方法,在子方法中使用 @Transactional注解并通过 rollbackFor 指定定回滚的异常 或者直接将 异常 抛出。
备注:在使用事务的时候,最好把子方法的异常进行抛出,交给调用的方法进行处理。