当前位置: 首页 > news >正文

事务Transactional注解的参数与失效场景分析

听说微信搜索《Java鱼仔》会变更强!

本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦

(一)前言

前段时间在面试的时候有个面试官问了这样一个问题,@Transactional注解能在私有方法上使用吗?如果现在这个问题问到你,你的答案是什么?

答案是在私有方法上添加@Transactional注解不能生效,平常在项目中没有用到就不会有这个印象,因此写了这篇文章来深入了解一下Transactional的用法。

(二)几个重要的参数

Transactional的作用是让被修饰的方法以事务的方式运行,所谓事务就是指这个方法中的所有持久化操作要么都进行,要么都不进行。比如订单系统中生成订单和库存删减需要是事务操作。

Transactional的使用很简单,在需要添加事务的方法处添加注解即可:

@Transactional(rollbackFor = Exception.class)
public int createOrder(String orderId) {
    //增加订单
    orderMapper.createOrder(orderId);
    //扣除账户金额
    accountMapper.updateAccount(10);
    return 1;
}

接下来介绍Transactional的重要的参数:

2.1 隔离级别 isolation

数据库中可能会遇到的三种问题:

脏读:一个事务读到另一个事务未提交的更新数据

不可重复读 : 前后多次读取,数据内容不一致

幻读 : 前后多次读取,数据总量不一致

Spring的隔离级别

  1. ISOLATION_DEFAULT: 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别.

  2. ISOLATION_READ_UNCOMMITTED:读未提交。这是事务最低的隔离级别,这种隔离级别会产生脏读,不可重复读和幻像读。

  3. ISOLATION_READ_COMMITTED:读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险。

  4. ISOLATION_REPEATABLE_READ: 可重复读,解决不可重复读的隔离级别,但还是有幻读风险。

  5. ISOLATION_SERIALIZABLE 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,解决脏读、不可重复读和幻读。

隔离级别并非越高越好,越高的隔离级别意味着越大的资源消耗,因此需要做适当取舍。

2.2 传播属性 propagation

事务的传播属性只在Spring中存在,在数据库中的事务中不存在传播属性的说法

Spring的传播属性有以下七种:

PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。(默认)

PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED–如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

2.2.1 重要传播属性的解释:

假设有ServiceA.MethodA(),在其内部调用ServiceB.MethodB()

PROPAGATION_REQUIRED
Spring默认使用PROPAGATION_REQUIRED传播属性,当ServiceA.MethodA()运行时,开启一个事务,此时ServiceB.MethodB()方法发现已经存在一个事务,就不会再开启事务,因此不管哪一个方法报错,都会回调。

PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。当ServiceA.MethodA()运行时,开启一个事务A。当运行ServiceB.MethodB()时,把事务A挂起,然后开启事务B。就算事务A发生回滚,事务B依然能正常提交。总结起来就是:外部事务不会影响内部事务的提交和回滚。

PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。关于嵌套需要首先了解检查点的概念:当在事务中设置检查点后,如果发生回滚,会回滚到设置检查点的位置,而不是回滚整个事务。嵌套事务就使用了检查点Savepoint。当ServiceA.MethodA()运行时,开启一个事务A。当运行ServiceB.MethodB()时,开启一个子事务B,它将取得一个 savepoint. 如果这个事务B失败, 将回滚到此 savepoint,而不会影响整个事务。总结一句话就是内部事务不会影响外部事务的提交和回滚。

2.3 readOnly

默认情况下是false,如果设置为true表示该方法是可读方法,如果存在数据的修改会抛出异常。

2.4 rollbackForClassName/rollbackFor

用来指明回滚的条件是哪些异常类或者异常类名。

2.5 norollbackForClassName/noRollbackFor

用来指明不回滚的条件是哪些异常类或者异常类名。

2.6 timeout

用于设置事务处理的时间长度,防止长事件的阻塞占用系统资源。

2.7 value

指定要使用的Spring事务管理器。

(三)什么情况下Transactional会失效

3.1 修饰非public方法时

这就是开头的问题了,当用Transactional修饰非public方法时,Transactional注解是不会生效的:

3.2 在类的内部调用事务方法

如果在一个类的内部调用了事务方法,Transactional也不会生效。

@Service
public class OrderServiceImpl implements OrderService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int createOrder(String orderId) {
        //增加订单
        orderMapper.createOrder(orderId);
        //扣除账户金额
        accountMapper.updateAccount(10);
        return 1;
    }
    
    public void innerTransfer(){
        createOrder("111");
    }
}

3.3 捕获异常后未抛出

默认情况下只会在捕获了RuntimeException后Transactional注解才会生效,如果在代码中捕获了异常后未抛出,则Transactional失效:

@Transactional(rollbackFor = Exception.class)
public int createOrder(String orderId) {
    //增加订单
    orderMapper.createOrder(orderId);
    //扣除账户金额
    accountMapper.updateAccount(10);
    try {
        int i=1/0;
    }catch (RuntimeException e){
        e.printStackTrace();
    }
    return 1;
}

解决办法之一是在catch后再抛出一次异常:

@Transactional(rollbackFor = Exception.class)
public int createOrder(String orderId) {
    //增加订单
    orderMapper.createOrder(orderId);
    //扣除账户金额
    accountMapper.updateAccount(10);
    try {
        int i=1/0;
    }catch (RuntimeException e){
        e.printStackTrace();
        throw new RuntimeException();
    }
    return 1;
}

(四)总结

Transactional注解需要重点关注两个参数:isolation、propagation,以及三种失效情况。另外如果存在数据源的切换,单体的Transactional将失效,这个时候就需要用到分布式事务,比如seata。

相关文章:

  • 个性化 UIAlertController
  • 如何在工作中快速上手Git
  • Dubbo两小时快速上手教程(直接代码、Spring、SpringBoot)
  • [改善Java代码]让工具类不可实例化
  • Sentinel快速入门:这可能是目前最好的分布式系统限流降级框架
  • [CareerCup] 17.8 Contiguous Sequence with Largest Sum 连续子序列之和最大
  • 想在一个项目中实现多数据源切换?几行代码就搞定了
  • 分享工作一年后收藏的超好用Idea插件,工作效率直接翻倍
  • Activity后台运行一段时间回来crash问题的分析与解决
  • SpringBoot+SpringSecurity实现基于真实数据的授权认证
  • Linux SendMail发送邮件失败诊断案例(三)
  • ElasticSearch究竟是个什么东西
  • 菜鸟学自动化测试(五)-----selenium命令之定位页面元素
  • 软件行业和互联网行业究竟有什么区别?又该如何去选择?
  • 今天打开阿里妈妈惊现 ¥50 元佣金
  • [译]如何构建服务器端web组件,为何要构建?
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • css属性的继承、初识值、计算值、当前值、应用值
  • Date型的使用
  • Java程序员幽默爆笑锦集
  • Java精华积累:初学者都应该搞懂的问题
  • mysql外键的使用
  • nodejs实现webservice问题总结
  • php中curl和soap方式请求服务超时问题
  • python docx文档转html页面
  • React Native移动开发实战-3-实现页面间的数据传递
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 基于游标的分页接口实现
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 实现简单的正则表达式引擎
  • 小程序 setData 学问多
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 数据可视化之下发图实践
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • (2)Java 简介
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (六)激光线扫描-三维重建
  • (十六)Flask之蓝图
  • (四)汇编语言——简单程序
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • *Django中的Ajax 纯js的书写样式1
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET 3.0 Framework已经被添加到WindowUpdate
  • .NET delegate 委托 、 Event 事件,接口回调
  • .Net mvc总结
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .Net 垃圾回收机制原理(二)
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET/C# 检测电脑上安装的 .NET Framework 的版本
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)