• Spring中的事务与事务传播机制


    事务

    在学习MySQL时我们学习过事务,而到了Spring的学习时,同样会学习到事务,两个东西的事务的定义都是一样的:将一组操作封装成一个执行单元,要么全部成功,要么全部失败

    在Spring中使用事务有两种方式

    一种是使用编程式事务

    1. @RestController
    2. @RequestMapping("/user")
    3. public class UserController {
    4. @Autowired
    5. private UserService userService;
    6. //编程式事务
    7. @Autowired
    8. private DataSourceTransactionManager dataSourceTransactionManager;//事务管理器 -> 所有的事务都是通过这个事务管理器来进行管理的
    9. @Autowired
    10. private TransactionDefinition transactionDefinition;
    11. @RequestMapping("/del")
    12. public int del(Integer id){
    13. if(id == null || id < 0){
    14. return -1;
    15. }
    16. //开启事务
    17. TransactionStatus transactionStatus =
    18. dataSourceTransactionManager.getTransaction(transactionDefinition);
    19. int result = userService.del(id);
    20. System.out.println("删除: " + result);
    21. // dataSourceTransactionManager.commit(transactionStatus);//
    22. dataSourceTransactionManager.rollback(transactionStatus);//为了不污染数据库,所以使用rollback就可以回滚事务
    23. return result;
    24. }
    25. }

    一种是使用声明式事务

    1. @RestController
    2. @RequestMapping("/user3")
    3. public class UserControllerTransaction {
    4. @Autowired
    5. private UserService userService;
    6. @Transactional
    7. @RequestMapping("/add")
    8. public int add(String username,String password){
    9. if (null == username || null == password || username.equals(" ") || password.equals(" ")){
    10. return 0;
    11. }
    12. User user = new User();
    13. user.setUsername("wangwu");
    14. user.setPassword("123456");
    15. int result = userService.add(user);
    16. return result;
    17. }
    18. }

    两者的区别在于一种是使用编写大量代码的方式启动事务,另一种是使用注解的方式启动事务

    很明显使用注解的方式启动事务的声明式事务更加简单好用,但是使用声明式事务也需要一定的注意:声明式事务只能作用于public修饰的方法上,如果作用在public修饰的类上那么该类的所有public方法都会被添加事务


    我们使用声明式事务时,如果方法内出现了异常但是异常被捕获了,那么此时我们的事务不会进行回滚操作

    1. @RequestMapping("/del")
    2. @Transactional
    3. public int del(Integer id){
    4. if (id == null || id <= 0){
    5. return 0;
    6. }
    7. try{
    8. int num = 10 / 0;
    9. }catch (Exception e){
    10. System.out.println(e);
    11. }
    12. return userService.del(id);
    13. }

    可以看到,当我们的代码内部出现了异常但是事务并没有出现回滚操作

    原因在于在代码里面自己把异常处理了,所以代码里面不会抛出异常

    当Transactional没有检测到异常的出现的时候那么就会认为代码式正常执行的

    所以事务不会进行回滚操作

    解决方案:1.不要添加try catch或者抛出异常让框架Transactional来感知异常,自动回滚

    1. try{
    2. int num = 10 / 0;
    3. }catch (Exception e){
    4. throw e;
    5. }

    2.自己手动添加回滚操作

    1. try{
    2. int num = 10 / 0;
    3. }catch (Exception e){
    4. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    5. }

    事务的四大特性:原子性,隔离性,持久性,一致性,其中Spring事务的隔离性与学习MySQL时遇到的隔离性几乎是完全一样的:

     Spring 中事务隔离级别包含以下 5 种:

    1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主

    2. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读

     3. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重 复读

    4. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级 别)

    5. Isolation.SERIALIZABLE:串行化,可以解决所有并发问题但是性能太低

    可以看到在Spring中只有Isolation.DEFAULT这个隔离级别是特有的,其余隔离级别与MySQL相同


    事务传播机制

    事务传播机制级别分为七个

    1.Propagation.REQUIRED:Spring默认的事务传播级别,如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就创建一个事务

    2.Propagation.SUPPORTS:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就以非事务的方式运行

    3.Propagation.MANDATORY:如果当前存在事务那么就加入当前事务,如果当前不存在事务,那么就抛出异常

    4.Propagation.REQUIRES_NEW:如果当前存在事务那么就挂起当前事务,重新开启自己的事务,也就是说无论当前是否存在事务,都会新开一个事务,开启的事务相互独立互不干扰

    5.Propagation.NOT_SUPPORTED:如果当前存在事务那么就挂起当前事务,以非事务的方式进行运行

    6.Propagation.NEVER:以非事务的方式进行运行,如果存在事务那么就抛出异常

    7.Propagation.NESTED:如果当前存在事务,那么就创建一个事务作为当前事务的嵌套事务进行运行,如果当前没有事务就等价于Propagation.REQUIRED

    这里重点说一下Propagation.NESTED,什么叫做嵌套事务呢?说白了就是记录一个保存点,当事务回滚的时候回滚到保存点的位置,使用Propagation.NESTED可以实现部分事务回滚的效果,因此之前的事务是不受影响的

    下面使用代码的方式演示部分事务传播机制级别

    Propagation.REQUIRED

    1. @RequestMapping("/save")
    2. @Transactional
    3. public Object save(User user) {
    4. // 插⼊⽤户操作
    5. userService.add(user);
    6. // 插⼊⽇志
    7. logService.add("⽤户插⼊:" + user.getUsername());
    8. return true;
    9. }
    10. @Service
    11. public class LogService {
    12. @Autowired
    13. private LogMapper logMapper;
    14. @Transactional(propagation = Propagation.REQUIRES)
    15. public int add(String content){
    16. int i = 10 / 0;
    17. Log log = new Log();
    18. log.setId(1);
    19. log.setMessage(content);
    20. return logMapper.add(log);
    21. }
    22. }
    23. @Service
    24. public class UserService {
    25. @Autowired
    26. private UserMapper userMapper;
    27. @Transactional(propagation = Propagation.REQUIRES)
    28. public int add(User user){
    29. int result = userMapper.add(user);
    30. return result;
    31. }
    32. }

    当我们的代码里面有错误时,整个事务都会被回滚 

    1.UserService程序执行完成

     2. LogService 保存⽇志程序报错,因为使⽤的是 Controller 中的事务,所以整个事务回滚

     3. 数据库中没有插⼊任何数据,也就是步骤 1 中的⽤户插⼊⽅法也回滚了


      REQUIRES_NEW

    1. @RequestMapping("/save")
    2. @Transactional
    3. public Object save(User user) {
    4. // 插⼊⽤户操作
    5. userService.add(user);
    6. // 插⼊⽇志
    7. logService.add("⽤户插⼊:" + user.getUsername());
    8. return true;
    9. }
    10. @Service
    11. public class LogService {
    12. @Autowired
    13. private LogMapper logMapper;
    14. @Transactional(propagation = Propagation.REQUIRES_NEW)
    15. public int add(String content){
    16. int i = 10 / 0;
    17. Log log = new Log();
    18. log.setId(1);
    19. log.setMessage(content);
    20. return logMapper.add(log);
    21. }
    22. }
    23. @Service
    24. public class UserService {
    25. @Autowired
    26. private UserMapper userMapper;
    27. @Transactional(propagation = Propagation.REQUIRES_NEW)
    28. public int add(User user){
    29. int result = userMapper.add(user);
    30. return result;
    31. }
    32. }

    执行结果:UserSevice顺利插入数据,LogService插入数据失败,但两者都没有影响UserController的执行


     Propagation.NEVER

    1. @RequestMapping("/save")
    2. @Transactional
    3. public Object save(User user) {
    4. // 插⼊⽤户操作
    5. userService.add(user);
    6. return true;
    7. }
    8. @Service
    9. public class UserService {
    10. @Autowired
    11. private UserMapper userMapper;
    12. @Transactional(propagation = Propagation.NEVER)
    13. public int add(User user){
    14. int result = userMapper.add(user);
    15. return result;
    16. }
    17. }

     程序直接报错,没有插入任何数据


      Propagation.NESTED

    1. @RequestMapping("/save")
    2. @Transactional
    3. public Object save(User user) {
    4. // 插⼊⽤户操作
    5. userService.add(user);
    6. return true;
    7. }
    8. @Service
    9. public class UserService {
    10. @Autowired
    11. private UserMapper userMapper;
    12. @Transactional(propagation = Propagation.NESTED)
    13. public int add(User user){
    14. int result = userMapper.add(user);
    15. // 插⼊⽇志
    16. logService.add("⽤户插⼊:" + user.getUsername());
    17. return result;
    18. }
    19. }
    20. @Service
    21. public class LogService {
    22. @Autowired
    23. private LogMapper logMapper;
    24. @Transactional(propagation = Propagation.NESTED)
    25. public int add(String content){
    26. int i = 10 / 0;
    27. Log log = new Log();
    28. log.setId(1);
    29. log.setMessage(content);
    30. return logMapper.add(log);
    31. }
    32. }

    执行结果:User和Log都没有插入数据

    原因在于:UserController调用了UserService,UserService执行完毕插入数据调用LogService,LogService出现异常,由于是嵌套事务,执行回滚操作,回滚到调用该方法的位置,回滚到UserService,由于代码中出现异常,所以UserService也需要进行回滚到调用该方法的位置,所以两个数据并没有插入

    但是如果说LogService出现异常的时候处理了异常,那么此时只有LogService会出现回滚而UserService可以不用回滚

    1. @Service
    2. public class LogService {
    3. @Autowired
    4. private LogMapper logMapper;
    5. @Transactional(propagation = Propagation.NESTED)
    6. public int add(String content){
    7. try{
    8. int num = 10 / 0;
    9. }catch (Exception e){
    10. TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    11. }
    12. Log log = new Log();
    13. log.setId(1);
    14. log.setMessage(content);
    15. return logMapper.add(log);
    16. }
    17. }

    从实现了部分回滚的操作


    Require和Nested两种事务传播机制级别的区别

    Require是将当前事务加入到整个事务当中,假如出现了回滚那么整个事务都需要进行回滚

    Nested是将当前事务嵌套到整个事务当中,假如出现了回滚那么可以实现部分事务回滚的操作,部分事务回滚到保存点(也就是调用该方法的代码处),不会影响上一个方法的执行结果

  • 相关阅读:
    CSDN客诉周报第3期|本周解决22个问题,实现2个用户需求
    【MySQL篇】 MySQL基础学习
    Jenkins pipeline中的全局变量
    麒麟操作系统提示“默认密钥环已上锁”的解决办法
    【车载开发系列】CAN总线知识扩展篇
    黄河.黄土.黄种人杂志黄河.黄土.黄种人杂志社黄河.黄土.黄种人编辑部2022年第15期目录
    大数据随记 —— 利用Python分析快手APP全国大学生用户数据(2022 年初赛第四题 )
    数据结构之队的实现
    hrust工程化学习(六)----最近邻搜索
    保姆级cat系统搭建过程
  • 原文地址:https://blog.csdn.net/Superkom666/article/details/132808948