@Transactional 注解使用
@Transactional
的使用方法,定义在方法上,表示方法上有事务,我们可以通过 propagation
属性来定义事务的传播机制
@Transactional public void test() { userMapper.insert(user); userService.a(); } @Transactional(propagation = Propagation.REQUIRED) public void a() { userMapper.insert(user); }
执行过程描述:
- 生成test事务状态对象
- test事务doBegin,获取并将数据库连接2825设置到test事务状态对象中
- 把test事务信息设置到事务同步管理器中
- 执行test业务逻辑方法(可以获取到test事务的信息)
4.1. 生成a事务状态对象,并且可以获取到当前线程中已经存在的数据库连接2825
4.2. 判断出来当前线程中已经存在事务
4.3. 如果需要新开始事务,就先挂起数据库连接2825,挂起就是把test事务信息从事务同步管理器中转移到挂起资源对象中,并把当前a事务状态对象中的数据库连接设置为null
4.4. a事务doBegin,新生成一个数据库连接2826,并设置到a事务状态对象中
4.5. 把a事务信息设置到事务同步管理器中
4.6. 执行a业务逻辑方法(可以利用事务同步管理器获取到a事务信息)
4.7. 利用a事务状态对象,执行提交
4.8. 提交之后会恢复所挂起的test事务,这里的恢复,其实只是把挂起资源对象中所 保存的信息再转移回事务同步管理器中
- 继续执行test业务逻辑方法(仍然可以获取到test事务的信息)
- 利用test事务状态对象,执行提交
事务传播机制
事务定义的常用几个案例
情况1
开启两个默认的事务定义,进行调用 test 方法调用 a 方法实现两个方法的正常执行
@Component public class UserService { @Autowired private UserService userService; @Transactional public void test() { // test方法中的sql userService.a(); } @Transactional public void a() { // a方法中的sql } }
默认情况下传播机制为REQUIRED 所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn
- 设置conn的autocommit为false
- 执行test方法中的sql
- 执行a方法中的sql
- 执行conn的commit()方法进行提交
情况2
开启两个默认的事务定义,进行调用在调用 test 的时候,先调用 a , 让 test 方法报错,实现两个方法事务的同时回滚。
@Component public class UserService { @Autowired private UserService userService; @Transactional public void test() { // test方法中的sql userService.a(); int result = 100/0; } @Transactional public void a() { // a方法中的sql } }
所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn
- 设置conn的autocommit为false
- 执行test方法中的sql
- 执行a方法中的sql
- 抛出异常
- 执行conn的rollback()方法进行回滚
情况3
开启两个默认的事务定义,进行调用在调用 test 的时候,先调用 a , 让 a 方法报错,实现两个方法事务的同时回滚。
@Component public class UserService { @Autowired private UserService userService; @Transactional public void test() { // test方法中的sql userService.a(); } @Transactional public void a() { // a方法中的sql int result = 100/0; } }
所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn
- 设置conn的autocommit为false
- 执行test方法中的sql
- 执行a方法中的sql
- 抛出异常
- 执行conn的rollback()方法进行回滚
情况4
开启两个默认的事务定义,进行调用在调用 test 的时候,先调用 a, a 是独立事务运行 , 让 a 方法报错,a 回滚,由于异常传播到 test 所以 test 也会回滚。
@Component public class UserService { @Autowired private UserService userService; @Transactional public void test() { // test方法中的sql userService.a(); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void a() { // a方法中的sql int result = 100/0; } }
所以上面这种情况的执行流程如下:
- 新建一个数据库连接conn
- 设置conn的autocommit为false
- 执行test方法中的sql
- 又新建一个数据库连接conn2
- 执行a方法中的sql
- 抛出异常
- 执行conn2的rollback()方法进行回滚
- 继续抛异常
- 执行conn的rollback()方法进行回滚
开发中的常见问题
- 场景:通过 aop 进行代理动态设置数据源,在一个事务中调用不同库中的操作出现第二个库执行 SQL 的时候报错提示找不到表,并且设置数据源切换失败。
案例: 在项目中通过 aop 去动态设置数据源,当 a 操作(在d1库中执行)完成后去调用 b 操作(在 d2库中执行)如果用 @Transactional
标记开启事务那么,将返回数据源切换异常。 解决方案: 在将 b 操作的数据的事务传播机制进行修改为 REQUIRES_NEW
完整定义 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = RuntimeException.class)
。spring-tx 源码位置 TransactionAspectSupport#invokeWithinTransaction, createTransactionIfNecessary