@Transactional 注解概述(IT枫斗者)

@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。

事务

  • 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。

编程式事务

  • 是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强。

  • 编程式事务方式需要开发者在代码中手动的管理事务的开启、提交、回滚等操作。

  • public void test() {TransactionDefinition def = new DefaultTransactionDefinition();TransactionStatus status = transactionManager.getTransaction(def);try {// 事务操作// 事务提交transactionManager.commit(status);} catch (DataAccessException e) {// 事务提交transactionManager.rollback(status);throw e;}
    }
    
  • 如以上代码,开发者可以通过API自己控制事务。

声明式事务

  • 基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了。

  • 声明式事务管理方法允许开发者配置的帮助下来管理事务,而不需要依赖底层API进行硬编码。开发者可以只使用注解或基于配置的 XML 来管理事务。

  • @Transactional@GetMapping("/test")public String test() {int insert = cityInfoDictMapper.insert(cityInfoDict);}
    
  • 声明式事务对代码没有侵入性,方法内只需要写业务逻辑就可以了,帮助我们节省了很多代码,他会自动帮我们进行事务的开启、提交以及回滚等操作,把程序员从事务管理中解放出来。可以说优点很明显,但是这种方式的缺点也很明显。

声明式事务的粒度问题

  • 首先,声明式事务有一个局限,那就是他的最小粒度要作用在方法上。
  • 也就是说,如果想要给一部分代码块增加事务的话,那就需要把这个部分代码块单独独立出来作为一个方法。
  • 在大事务耗时过长需要高并发优化的情况下不建议使用声明式事务,建议采用上面提到的编程式事务方式。

声明式事务容易被开发者忽略

  • 而事务一旦被忽略就容易造成很多故障
  • 首先,如果开发者没有注意到一个方法是被事务嵌套的,那么就可能会再方法中加入一些如RPC远程调用、消息发送、缓存更新、文件写入等操作。
  • 我们知道,这些操作如果被包在事务中,有两个问题:
    • 这些操作自身是无法回滚的,这就会导致数据的不一致。可能RPC调用成功了,但是本地事务回滚了,可是PRC调用无法回滚了(这里不讨论分布式事务)。
    • 在事务中有远程调用,就会拉长整个事务。那么久会导致本事务的数据库连接一直被占用,那么如果类似操作过多,就会导致数据库连接池耗尽。
  • 有些时候,即使没有在事务中进行远程操作,但是有些人还是可能会不经意的进行一些内存操作,如运算。或者如果遇到分库分表的情况,有可能不经意间进行跨库操作。

声明式事务用不对在某些场景下容易失效

  • 这个问题后面我们着重讲述一下失效的几种场景。
  • 下面我们就讲讲声明式事务使用的@Transactional注解。

@Transactional介绍

@Transactional 可以作用在接口、类、类方法

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。

  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效。

  •  @Transactional@RestController@RequestMappingpublic class MybatisPlusController {@Autowiredprivate CityInfoDictMapper cityInfoDictMapper;@Transactional(rollbackFor = Exception.class)@GetMapping("/test")public String test() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setParentCityId(2);cityInfoDict.setCityName("2");cityInfoDict.setCityLevel("2");cityInfoDict.setCityCode("2");int insert = cityInfoDictMapper.insert(cityInfoDict);return insert + "";}
    }
    

@Transactional注有哪些属性?

  • 属性 类型 描述
    value String 可选的限定描述符,指定使用的事务管理器
    propagation enum: Propagation 可选的事务传播行为设置
    isolation enum: Isolation 可选的事务隔离级别设置
    readOnly boolean 读写或只读事务,默认读写
    timeout int (in seconds granularity) 事务超时时间设置
    rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
    rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
    noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
    noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

propagation属性

  • propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )
    Propagation.SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
    Propagation.MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
    Propagation.REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
    Propagation.NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务。
    Propagation.NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常。
    Propagation.NESTED 和 Propagation.REQUIRED 效果一样。

isolation 属性

  • isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。
  • Isolation.DEFAULT:使用底层数据库默认的隔离级别。
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

timeout 属性

  • timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

  • 示例:

  • @Service
    public class UserServiceImpl implements UserService{
    // 加载mapper
    @Autowired
    private UserMapper usermapper;@Transactional(propagation=Propagation.REQUIRED,timeout=5) // 启用事务管理
    public void myTransaction(User addUser, User updateuser) {System.out.println("---- UserServiceImpl  myTransaction方法开始 ");usermapper.add(addUser);try {Thread.sleep(7000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}usermapper.update(updateuser);System.out.println("---- UserServiceImpl  myTransaction方法结束 ");
    }
  • 运行结果:

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2yLx1wQR-1684801592569)(C:\Users\quyanliang\AppData\Roaming\Typora\typora-user-images\1684799585238.png)]

  • 最终,由于事务超时,事务回滚。

  • 运行的时候,由于事务超时,就会报错 TransactionTimedOutException:…

readOnly 属性

  • readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

  • rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

  • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transactional失效场景

  • 接下来我们结合具体的代码分析一下哪些场景下,@Transactional 注解会失效。

@Transactional 应用在非 public 修饰的方法上

  • 如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FDcTRuLs-1684801592570)(C:\Users\quyanliang\AppData\Roaming\Typora\typora-user-images\1684799666803.png)]

  • 之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

  • protected TransactionAttribute computeTransactionAttribute(Method method,    Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;
    }
  • 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

  • 注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

@Transactional 注解属性 propagation 设置错误

  • 这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

@Transactional 注解属性 rollbackFor 设置错误

  • rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJw3Z2mD-1684801592570)(C:\Users\quyanliang\AppData\Roaming\Typora\typora-user-images\1684799736303.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kqjzftCM-1684801592570)(C:\Users\quyanliang\AppData\Roaming\Typora\typora-user-images\1684799745454.png)]

  • // 希望自定义的异常可以进行回滚

  • @Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

  • 若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

  • private int getDepth(Class<?> exceptionClass, int depth) {if (exceptionClass.getName().contains(this.exceptionName)) {// Found it!return depth;}// If we've gone as far as we can go and haven't found it...if (exceptionClass == Throwable.class) {return -1;}return getDepth(exceptionClass.getSuperclass(), depth + 1);
    }
    

同一个类中方法调用,导致@Transactional失效、

  • 开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

  • 那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

  •  //@Transactional@GetMapping("/test")private Integer A() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("2");/*** B 插入字段为 3的数据*/this.insertB();/*** A 插入字段为 2的数据*/int insert = cityInfoDictMapper.insert(cityInfoDict);return insert;}@Transactional()public Integer insertB() throws Exception {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("3");cityInfoDict.setParentCityId(3);return cityInfoDictMapper.insert(cityInfoDict);}

异常被你的 catch“吃了”导致@Transactional失效

  •      @Transactionalprivate Integer A() throws Exception {int insert = 0;try {CityInfoDict cityInfoDict = new CityInfoDict();cityInfoDict.setCityName("2");cityInfoDict.setParentCityId(2);/*** A 插入字段为 2的数据*/insert = cityInfoDictMapper.insert(cityInfoDict);/*** B 插入字段为 3的数据*/b.insertB();} catch (Exception e) {e.printStackTrace();}}
  • 如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

  • 答案:不能!

  • 会抛出异常:1org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

  • 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

  • spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

  • 在业务方法中一般不需要catch异常,如果非要catch一定要手动抛出throw new RuntimeException(),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

数据库引擎不支持事务

  • 这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

种草链接 : 学习或者部署项目需要云产品,枫哥给大家申请到福利,现有64款云产品免费试用:https://click.aliyun.com/m/1000371850/

@Transactional 注解概述(IT枫斗者)相关推荐

  1. spring的@Transactional注解详细用法

    概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性. Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型 ...

  2. SpringCloud学习笔记009---杂七杂八003:spring的@Transactional注解详细用法

    概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性. Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型 ...

  3. spring,mybatis事务管理配置与@Transactional注解使用[转]

    spring,mybatis事务管理配置与@Transactional注解使用[转] spring,mybatis事务管理配置与@Transactional注解使用 概述 事务管理对于企业应用来说是至 ...

  4. spring,mybatis事务管理配置与@Transactional注解使用

    spring,mybatis事务管理配置与@Transactional注解使用 概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性. Spring Framewor ...

  5. @Transactional注解最容易忽视的三个失效场景!

    @Transactional注解在以下场景中使用,是会失效的,切记! 1.非public方法 spring对注解事务的方法进行校验,修饰符是不是public,不是 public则不会获取@Transa ...

  6. @aspect注解类不生效_springboot:@Transactional注解 VS @Service注解

    1. Transactional注解与Service/Component注解冲突? 之前遇到一个神奇的事情--用Transactional注解的方法,数据处理了一半,后面的数据处理抛出异常后,没有回滚 ...

  7. 一口气说出 6 种 @Transactional 注解的失效场景

    一.事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚等操作,代码侵入性比较强, ...

  8. spring@Transactional注解事务不回滚不起作用无效的问题处理

    这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚.后来终于找到了原因. 如果你也出现了这种情况,可以从下面开始排查. 一.特性 先来了解一下@Transactio ...

  9. @Transactional注解在什么情况下失效?

    引言 1.@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功.要么同时失败. 2.使用@Transactional注解时需要注意许 ...

最新文章

  1. server2016 查看图片 预览图片
  2. Java mail 发送邮件 主题(标题)乱码
  3. cobbler基础安装
  4. 软件工程学习笔记《三》代码优化和性能测试
  5. 微信公众号 模版消息 跳转到小程序报错{ errcode: 40013, errmsg: 'invalid appid hint: [cC6RwA09011295]' }
  6. linux优化pdf,linux系统安全和优化.pdf
  7. TCL_事务控制语言
  8. cordova构建项目命令小结
  9. Opencv环境配置
  10. DButils基本使用
  11. 解决Windows x86网易云音乐不能将音乐下载到SD卡的BUG
  12. 配置Ubuntu以及ssh
  13. 在使用vue脚手架之前你必须掌握的:vue的模版以及路由用法
  14. APP下载链接在微信内打不开显示已停止访问该网页怎么办?
  15. Blend for Visual Studio 让XAML也可以像WinForm一样可视化设计,Blend 与Studio的区别
  16. STM32 F105 USB CDC host
  17. Mac 双系统bootcamp不能正常卸载windows系统
  18. 使用Guardium和WebSphere Application Server监视应用程序用户的数据库活动
  19. 53KF 客服系统主动发起设置技巧
  20. JavaScript中this的指向

热门文章

  1. 绝地求生键位魔改v3.0
  2. whatsApp皮肤配置流程
  3. (非常详细)大数据平台学习·环境安装配置(一)(RockyLinux9版)
  4. linux数据入表格,数据导入,导出 表操作
  5. 【新学期】双非本科大三学长经验分享
  6. Raft协议详解(一)前言:子问题分解
  7. Python GIL锁问题探究及解决
  8. 穿越火线登录上去一直连接服务器,Windows7登录CF时提示连接服务器失败的解决方法...
  9. 2018年高考631选计算机,2018年高考各省大学录取分数线查询
  10. AppStore新应用上传指南-追信魔盒