目录
一. Spring 中事务的实现
1. 编程式事务
2. 声明式事务
3. @Transaction 参数的设置
4. @Transaction 的隔离级别
5. @Transaction 的工作原理
二. Spring 事务传播机制
七种事务传播机制
支持当前事务
不支持当前事务
嵌套事务
一. Spring 中事务的实现
1. 编程式事务
编程式事务,主要就是三个步骤:
1. 开启事务;
2. 提交事务;
3. 回滚事务;
在 SpringBoot 中内置了两个对象:DataSourceTransactionManager 用来开启事务,提交事务和回滚事务,TransactionDefinition 是事务的属性,在开启事务的时候,就需要传入此对象,从而获得一个事务 TransactionStatus;
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;// 编程式事务 要引入两个内置的对象
// 整个事务的操作,是需要到这个事务管理器对象的:DataSourceTransactionManager@Autowiredprivate DataSourceTransactionManager transactionManager;@Autowired
// 事务定义器,通过这个来设置事务的一些东西,要得到一个事务,就必须传递此参数private TransactionDefinition transactionDefinition;@RequestMapping("/del")public int del(Integer id) {if (id == null || id <= 0) {return 0;}
// 开启事务TransactionStatus transactionStatus = null;int result = 0;try {
// 开启事务transactionStatus = transactionManager.getTransaction(transactionDefinition);// 业务操作,删除用户result = userService.del(id);System.out.println("删除:" + result);
// 正常情况下,提交事务transactionManager.commit(transactionStatus);} catch (Exception e) {
// 如果出现异常了,回滚事务, 或者事务还没创建,就不用回滚if (transactionStatus != null) {transactionManager.rollback(transactionStatus);}}return result;}
}
2. 声明式事务
相比于编程式事务,声明式事务就方便得多了,只需要在方法或类上添加注解 @Transaction
不需要手动开启和提交事务,进入方法就自动开启事务,方法执行完就自动提交事务,如果中途发生没有处理的异常,就会自动回滚事务;
需要注意的是:
@Transactional 修饰方法的时候,只能修饰在 public 方法上;
修饰类的时候,表示该注解对类中的所有 public 方法生效;
@RestController
@RequestMapping("/user2")
public class UserController2 {@Autowiredprivate UserService userService;@RequestMapping("/del")@Transactional // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务public int del(Integer id){if (id == null || id <= 0){return 0;}int result = userService.del(id);System.out.println("删除:"+result);int num = 10 / 0; // 制造算数异常 return result;}}
此时因为在运行过程中,出现了算数异常,所以事务会进行回滚,页面会以 500 的形式返回;
但如果对于异常进行 try/catch 处理的时候,情况就不一样了,如果是我们自己去抓取了异常,没有让异常抛出,此时就相当于是我们自己把异常消化了,外部监测不到,就不会回滚事务了,而是正常提交事务;
对此就有两种解决方法:
1. 将异常抛出,让框架感知到异常,当框架感知到异常之后,就会自动进行回滚;
2. 手动进行回滚事务;
try {int num = 10 / 0;}catch (Exception e){
// e.printStackTrace(); // 不回滚
// throw e; // 回滚
// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
3. @Transaction 参数的设置
4. @Transaction 的隔离级别
在前面的文章中,有说到事务的四大特性:原子性,一致性,持久性,隔离性。在隔离性中,也有五种隔离级别:
1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主;2. Isolation.READ_UNCOMMITTED:读未提交;3. Isolation.READ_COMMITTED:读已提交;4. Isolation.REPEATABLE_READ:可重复读;5. Isolation.SERIALIZABLE:串行化;此处的细节内容可以见前面的文章。
5. @Transaction 的工作原理
@Transaction 是基于 AOP 来实现的,AOP 又是使用动态代理来实现的,如果目标对象实现了接口, 默认情况下会采用 JDK 的动态代理,如果目标对象没有实现接口,会采用 CGLIB 动态代理;
@Transaction 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务,如果中途遇到异常,则回滚事务;
二. Spring 事务传播机制
Spring 事务传播机制定义了多个包含了事务的方法,相互调用的时候,事务是如何在这些方法间进行传递的。
事务的隔离级别是保证多个事务并发执行的稳定性,事务的传播机制是保证一个事务在多个调用方法间的稳定性;
事务传播机制解决:
七种事务传播机制
1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。
2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以非事务的方式继续运⾏。3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不⼲扰。5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
也可以进行整体划分:
此处写一个 添加用户和添加日志 的案例进行演示:
首先在 Controller 层中的 add 方法进行 @Transaction 注解添加,然后调用 Service 层的 add 方法,Service 层的 add 方法也添加了 @Transaction 注解,然后调用 logService.add() 方法进行日志打印,logService.add() 方法也添加了 @Transaction 注解;
( 此处为了方便分析,用 Controller 调用了 Service,后 Service 再次调用 Service 层的方法)
支持当前事务
@RestController
@RequestMapping("/user3")
public class UserController3 {@Autowiredprivate UserService userService;@RequestMapping("/add")@Transactional(propagation = Propagation.REQUIRED)public int add(String username,String password){if (null == username || null == password ||username.equals("") || password.equals("")) return 0;UserInfo user = new UserInfo();user.setUsername(username);user.setPassword(password);int result = userService.add(user);
// 外部事务的回滚,不会报错误
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.REQUIRED)public int add(UserInfo userInfo){
// 给用户表添加用户信息int addUserResult = userMapper.add(userInfo);System.out.println("添加用户结果:"+addUserResult);// 添加日志信息LogInfo logInfo = new LogInfo();logInfo.setMessage("添加用户信息");logService.add(logInfo);return addUserResult;}
}
@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRED)public int add(LogInfo log){int result = logMapper.add(log);System.out.println("添加日志:" + result);
// 设置回滚操作 内部事务的回滚会报错误TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}
此时的事务传播机制属于 支持当前事务 , 也就是可以将三个事务看为是一个事务,同时提交,同时回滚,在程序中,前两个事务都正常运行,然后最后一个事务进行回滚,此时整个事务都会进行回滚,也就是既没有添加用户,也没有添加日志。
此处需要注意:内部事务的回滚会以报错误的形式出现,显示 500 页面,外部事务的回滚则不会,上述是内部事务的回滚,所以显示的页面是:
不支持当前事务
将 LogService 中的 add 方法,也就是日志添加的方法,隔离级别设为 REQUIRES_NEW 。
此时该事务就与前面两个事务没有任何联系,不在一个调用链上的事务,各自执行相互不干扰。独自进行提交事务,回滚事务。
@Service
public class LogService {@Autowiredprivate LogMapper logMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public int add(LogInfo log){int result = logMapper.add(log);System.out.println("添加日志:" + result);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return result;}
}
此时在该方法中进行回滚操作,外部事务都不回滚,看一下执行结果:日志记录没有添加,而用户信息添加了;
需要补充的是:在 不支持当前事务 的机制中,虽然两个事务不在一个调用链上,但如果内层事务出现了异常,而没有及时处理,让外层的 Controller 和 Service 都感知到了,会导致整个程序报错,整个调用链都是500,虽然都是独自的业务,但是感知到异常的时候,都是会进行回滚的;
按照上述代码,如果在 LogService 中 (或者 UserService )的 add 方法加入 int num = 100/0 语句形成算数异常,此时内层事务对异常的未处理会导致外层也受影响,页面显示 500,日志 和 用户信息 都无法添加成功;
但如果是外层事务的异常未处理,也会造成 500 页面显示,但不会影响到内层事务的业务执行;
按照上述代码,如果在 UserController3 中的 add 方法加入 int num = 100/0 语句形成算数异常,此时日志可以正常添加,而用户信息无法添加,因为异常的出现而回滚事务了,页面显示为 500;
嵌套事务
嵌套事务,在进行回滚的时候,就只会影响到自己的内层事务,外层事务是影响不到的,也就是不会回滚嵌套之前的事务;
在上述代码中,LogService 中的事务,就算是 UserService 的内层事务,所以 UserService 中的事务设置为 嵌套事务 的时候,进行回滚就只会影响到 LogService 中的事务,会一起回滚,而最外层的 UserController 事务不会被影响到。
得到的结果也就是:用户信息添加失败,日志信息添加失败;
如果是对最里层事务 LogService 设置为 嵌套事务,回滚发生在 LogService 中,那么此时用户信息是可以添加成功的,但日志信息添加失败;
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate LogService logService;@Transactional(propagation = Propagation.NESTED)public int add(UserInfo userInfo){
// 给用户表添加用户信息int addUserResult = userMapper.add(userInfo);System.out.println("添加用户结果:"+addUserResult);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();// 添加日志信息LogInfo logInfo = new LogInfo();logInfo.setMessage("添加用户信息");logService.add(logInfo);return addUserResult;}
}
嵌套事务之所以能实现部分事务的回滚,是因为事务中有一个保存点的概念,嵌套事务进入之后就相当于新建了一个保存点,而回滚的时候,只会回滚到保存点,因此之前的事务是不受影响的。
嵌套事务和加入事务的区别:
1. 如果事务全部执行成功,二者的结果是一样的;
2. 如果事务执行到一半失败了,加入事务整个事务都会回滚,而嵌套事务会局部回滚,不会影响到上一个方法中执行的结果;