事务
概述
通过动态代理的方式实现数据库事务,是否为 Bean 类创建代理对象的判断标准:
- 类头上使用了注解 @Transactional
- 类中至少有一个方法使用了注解 @Transactional
代理对象在执行某个方法时会进入 TransactionInterceptor # invoke() 方法,该方法的执行逻辑是:
- 使用所配置的 PlatformTransactionManager 事务管理器新建一个数据库连接
- 修改数据库连接的 autocommit 为 false
- 执行 MethodInvocation.proceed() 方法 - 即执行业务方法
- 提交 - 如果有抛出异常则回滚
事务的完整执行流程
下图的文档在 180 阿里云盘的:文档 -> pro

案例
setGlobalRollbackOnParticipationFailure 和 catch

子事务的回滚案例

手动回滚
catch 异常后要手动强制回滚

事物失效的场景
访问权限
4中访问权限 private , default , protect , public 中只有最后一个 public 类型的方法支持事务
static , final
被这两个关键字修饰的方法也不支持
调用兄弟方法
例如下面的使用方法,会导致 methodA 的事务失效
@Service
public class TestService {
@Transactional
public void methodA(){
methodB();
methodC();
}
public void methodB(){}
public void methodC(){}
}
方法一:将其他兄弟方法重构在另外一个类中 方法二:注入自己,使用被注入的自己调用兄弟方法,使用该方法要在配置文件中开启设置
spring:
main:
allow-circular-references: true
@Service
public class TestService {
@Autowired
private TestService testService;
@Transactional
public void methodA(){
testService.methodB();
testService.methodC();
}
public void methodB(){}
public void methodC(){}
}
方法三:使用代理类调用兄弟方法
@Service
public class TestService {
@Transactional
public void methodA(){
}
/**
* 这里调用methodA() 的事务将会生效
*/
public void methodB(){
((TestService)AopContext.currentProxy()).methodA();
}
}
未被 spring 管理
没有使用 @Service , @Component 等纳入容器的注解,导致类没有被 spring 管理自然无法使用 spring 的事务
多线程环境
例如下面方法 add 中使用多线程调用了 doOtherThing ,当该方法出现异常时不会触发回滚
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
表不支持事务
MYSQL的MYISAM引擎不支持,如果创建表时使用了该引擎则该表不支持事务
未开启事务
springboot 默认是开启的,但是传统的 spring mvc 需要手动在配置文件中开启
传播特性设置不对
REQUIRED - 默认配置,如果上下文已存在事务则加入,如果没有则新建一个事务 SUPPORTS - 如果上下文存在事务则加入,如果不存在则以非事务的方式运行 MANDATORY - 如果上下文已有事务则执行,没有事务则抛出异常 REQUIRES_NEW - 每次都会新建一个事务,并将上下文已经存在的事务挂起,执行完本事务后再恢复上下文事务 NOT_SUPPORTED - 如果上下文中存在事务则挂起,保证本方法运行在非事务中,然后恢复上下文事务 NEVER - 上下文中存在事务则抛出异常,保证本方法运行在非事务中 NESTED - 上下文存在事务则嵌套事务执行,否则新建事务执行
错误的异常
自己处理掉异常不抛出则不会回滚,同时不正确的异常类型也不会回滚,例如下面的普通异常 Exception不会回滚。spring 只会针对运行时异常和错误回滚。
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
如果自定义的异常没有继承 RuntimeException,也会导致不回滚,但是可以通过指定异常类型来触发回滚
@Slf4j
@Service
public class UserService {
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}
例如上面使用 Transactional 的同时指定了自定义的异常类,当抛出该类型的异常时候也会回滚。注意这里又又坑了,如果方法内部抛出了SqlException 等其他异常又会导致不触发回滚。
回滚保存点问题
设计意图是 doOtherThing 方法回滚不影响外层的 add 方法,代码如下
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
但实际情况是如果 doOtherThing 方法出现异常导致回滚了同时也会导致 add 方法回滚,因为 doOtherThing 的异常冒泡特性导致方法 add 也收到了异常,进而导致 userMapper.insertUser 方法也回滚了。要实现上面提到的设计意图,即使 doOtherThing 回滚不会导致 userMapper.insertUser 方法的回滚,可以这样写
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
将 doOtherThing 抛出的异常捕获,不继续向上抛即可。
