相关推荐recommended
SpringBoot @Transactional事务详解
作者:mmseoamin日期:2023-12-25

事务用处及作用

事务主要是保证数据统一、一致的一种操作。

详细的一些专用术语在此这里不会说太多,如需了解自行百度了(还不是枯燥乏味),大致就是这意思。

事务用处

比如坤坤,坤坤拿着100元去买鸡,一个鸡10元,在没有事务的情况下,坤坤把100元交给了卖鸡老板,此时城管来了,老板突然跑路(这里指的是在支付时,数据出现异常),那么坤坤的100元也就没了,鸡也没买到。坤坤哭死…

如果有事务的场景下,坤坤拿着100元去买鸡,坤坤把100元给了卖鸡老板,老板突然跑路(发生异常),支付系统会自动回滚,把100元还给坤坤,这样数据就不会出错了。还有个场景,比如卖鸡老板正在找给坤坤钱的时候,此时另一个人长得也像坤坤的人来买鸡(这里指多线程并发),老板此时如果顾不过来,找钱就会出现问题,比如少给了坤坤20元,或者多给了坤坤20元等等操作。

SpringBoot @Transactional事务详解,在这里插入图片描述,第1张

当Java中一个方法内有多次对数据库的增删改查等操作,并且这些操作之间有一些关联关系,如果方法执行一半出问题报错,后面的操作将不会执行,造成数据异常,但是使用了事务以后可以如果中途执行失败,可以回退到方法执行之前,保证数据不出问题。

总之就是事务保障了数据交互时的安全性,数据要么都修改、要么都回滚。

事务四大特性

1、原子性

事务要么全部都被执行,要么就全都不被执行,如果有子事务提交失败,那么其他子事务对数据库的操作将被回滚,数据库回到事务提交前的状态;如果全部子事务都提交成功,则所有的数据库操作都会被提交

2、一致性

事务的执行使得数据库从一种正确状态转换成另一种正确状态

3、隔离性

一个事务的执行不能被其他事务所影响

4、持久性

事务一旦提交,就会永久保存在数据库中,及时数据库服务器发生故障,也不会丢失提交事务的操作。

事务四大隔离级别

  1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读,不可重复读) 基本不使用
  2. @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
  3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
  4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化

脏读:指的是,线程1修改值后,线程2读取该值,然后线程1又撤回修改,这就是脏读,一般在update会出现。

不可重复读:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据。那么,在第一个事务的两次读数据之间。由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样

幻读:待补充

更详细:http://outofmemory.cn/sjk/10012534.html

事务实现方式

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。

Spring事务实现主要有两种方法:编程式:beginTransaction()、commit()、rollback()等事务管理相关的方法。

还有声明式,使用注解@Transactional

正常情况下来说,SpringBoot默认是有事务的,默认也还会开启,不需要在Application.java类中在次声明@EnableTransactionManagement注解。

注意:@Transactional注解如果放到不是public修饰的方法或类上会导致事务失效。如果使用mysql引擎是MyISAM那么事务也不会起作用,MyISAM不支持事务,可以改为InnoDB引擎。

建立模拟数据库

这里我创建了3张表,分别是:

  • d_user: 用户表
  • d_detail:明细表
  • d_shop:商品表

    SpringBoot @Transactional事务详解,在这里插入图片描述,第2张

    SpringBoot @Transactional事务详解,在这里插入图片描述,第3张

    SpringBoot @Transactional事务详解,在这里插入图片描述,第4张

    d_shop看到这张表了吗?为了业务连贯性和方便理解,这里一定要注意!因为它没用。

    事务注解

    到这里你就不得了了,你现在已经很牛批了,最起码知道事务是什么,不加事务是什么后果。

    接下来学会实际应用那还得了?

    添加事务是用的是@Transactional注解。

    • @Transactional,不加任何参数时,默认会回滚运行时异常及其子类,其它范围之外的异常 Spring 不会帮我们去回滚数据
    • @Transactional(rollbackFor = Exception.class),如果加上rollbackFor 参数,会回滚所有异常类,前提下一定要在catch中抛出相关异常类,否则事务还是失效的。

      下面是异常类和子类关系图,@Transactional,不加任何参数时,默认只会回滚RuntimeException和子类,其他类不会回滚:

      SpringBoot @Transactional事务详解,在这里插入图片描述,第5张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第6张

      不使用事务

      1、第一种情况,不使用任何事务

      SpringBoot @Transactional事务详解,在这里插入图片描述,第7张

      这种操作,就是没有使用任务事务的,如果程序不出错,那么数据正常执行没有问题。

      金额 - 10,并且明细表中有购买记录。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第8张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第9张

      2、第二种情况

      如果这时候,某些程序发生错误,比如下面最常见的 不能被0整出异常。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第10张

      会发现,钱扣了… 却没有明细,没有明细就意味着数据不完整。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第11张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第12张

      后台完美的抛出了异常

      SpringBoot @Transactional事务详解,在这里插入图片描述,第13张

      使用事务

      1、第一种,只有@Transactional注解,这种情况只会 回滚 RuntimeException和子类,其他异常将不会回滚,这种不用特意抛出异常。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第14张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第15张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第16张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第17张

      即使后台出错,并且还保持数据的完整性。

      2、第二种,使用 @Transactional(rollbackFor = Exception.class) 注解,默认会回滚所有事务,前提下,一定要主动抛出异常,否则事务是不会生效的。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第18张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第19张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第16张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第17张

      3、第三种,可以指定回滚事务异常类,这里需要排除不用回滚的异常类,同样需要异常抛出。

      SpringBoot @Transactional事务详解,在这里插入图片描述,第22张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第19张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第16张

      SpringBoot @Transactional事务详解,在这里插入图片描述,第17张

      4、第四种,可以不依赖 throw new NullPointerException("") 手动执行事务回滚。

      // 手动回滚
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      
      @Override
          @Transactional(rollbackFor = {Exception.class})
          public boolean payShop(String shopId) {
              // 查询商品金额
              DShop dShop = dShopMapper.selectById(shopId);
              // 查询用户现有金额
              DUser dUser = dUserMapper.selectById("1");
              // 更新用户金额
              double newMoney = dUser.getMoney()-dShop.getShopMoney();
              dUser.setMoney(newMoney);
              dUserMapper.updateById(dUser);
              try {
                  String a = null;
                  boolean equals = a.equals("2");
              } catch (Exception e) {
                  e.printStackTrace();
                  // 手动执行回滚
                  TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
              }
              /*try {
                  String[] str = new String[1];
                  System.out.println(str[5]);
              } catch (Exception e) {
                  e.printStackTrace();
                  // 如果设置的 @Transactional(rollbackFor = {Exception.class}) 那么一定要抛出相关异常,否则事务不生效
                  throw new ArrayIndexOutOfBoundsException("数组超了");
              }*/
              DDetail dDetail = new DDetail();
              dDetail.setUserId(dUser.getUserId());
              dDetail.setPayMoney(dShop.getShopMoney());
              dDetail.setShopId(dShop.getShopId());
              dDetailMapper.insert(dDetail);
              return true;
          }
      

      效果是一样的,就不贴图片了。

      其他参考

      https://blog.csdn.net/yuxiangdeming/article/details/125243814