多方法调用 一个出错 集体回滚_一个@Transaction哪里来这么多坑?

前言

在之前的文章中已经对Spring中的事务做了详细的分析了,这篇文章我们来聊一聊平常工作时使用事务可能出现的一些问题(本文主要针对使用@Transactional进行事务管理的方式进行讨论)以及对应的解决方案

  1. 事务失效

  2. 事务回滚相关问题

  3. 读写分离跟事务结合使用时的问题

事务失效

事务失效我们一般要从两个方面排查问题

数据库层面

数据库层面,数据库使用的存储引擎是否支持事务?默认情况下MySQL数据库使用的是Innodb存储引擎(5.5版本之后),它是支持事务的,但是如果你的表特地修改了存储引擎,例如,你通过下面的语句修改了表使用的存储引擎为MyISAM,而MyISAM又是不支持事务的

alter table table_name engine=myisam;

这样就会出现“事务失效”的问题了

「解决方案」:修改存储引擎为Innodb

业务代码层面

业务层面的代码是否有问题,这就有很多种可能了

  1. 我们要使用Spring的声明式事务,那么需要执行事务的Bean是否已经交由了Spring管理?在代码中的体现就是类上是否有@ServiceComponent等一系列注解

「解决方案」:将Bean交由Spring进行管理(添加@Service注解)

  1. @Transactional注解是否被放在了合适的位置。在上篇文章中我们对Spring中事务失效的原理做了详细的分析,其中也分析了Spring内部是如何解析@Transactional注解的,我们稍微回顾下代码:

c5082a1494464ef7bffb5a70fc3a6a31.png
注解解析

❝代码位于:AbstractFallbackTransactionAttributeSource#computeTransactionAttribute中❞

也就是说,默认情况下你无法使用@Transactional对一个非public的方法进行事务管理

「解决方案」:修改需要事务管理的方法为public

  1. 出现了自调用。什么是自调用呢?我们看个例子

@Servicepublic class DmzService {public void saveAB(A a, B b) {
  saveA(a);
  saveB(b);
 }@Transactionalpublic void saveA(A a) {
  dao.saveA(a);
 }@Transactionalpublic void saveB(B b){
  dao.saveB(a);
 }
}

上面三个方法都在同一个类DmzService中,其中saveAB方法中调用了本类中的saveAsaveB方法,这就是自调用。在上面的例子中saveAsaveB上的事务会失效

那么自调用为什么会导致事务失效呢?我们知道Spring中事务的实现是依赖于AOP的,当容器在创建dmzService这个Bean时,发现这个类中存在了被@Transactional标注的方法(修饰符为public)那么就需要为这个类创建一个代理对象并放入到容器中,创建的代理对象等价于下面这个类

public class DmzServiceProxy {private DmzService dmzService;public DmzServiceProxy(DmzService dmzService) {this.dmzService = dmzService;
    }public void saveAB(A a, B b) {
        dmzService.saveAB(a, b);
    }public void saveA(A a) {try {// 开启事务
            startTransaction();
            dmzService.saveA(a);
        } catch (Exception e) {// 出现异常回滚事务
            rollbackTransaction();
        }// 提交事务
        commitTransaction();
    }public void saveB(B b) {try {// 开启事务
            startTransaction();
            dmzService.saveB(b);
        } catch (Exception e) {// 出现异常回滚事务
            rollbackTransaction();
        }// 提交事务
        commitTransaction();
    }
}

上面是一段伪代码,通过startTransactionrollbackTransactioncommitTransaction这三个方法模拟代理类实现的逻辑。因为目标类DmzService中的saveAsaveB方法上存在@Transactional注解,所以会对这两个方法进行拦截并嵌入事务管理的逻辑,同时saveAB方法上没有@Transactional,相当于代理类直接调用了目标类中的方法。

我们会发现当通过代理类调用saveAB时整个方法的调用链如下:

df43bb50afd62dc528f3757d83baabff.png

实际上我们在调用saveAsaveB时调用的是目标类中的方法,这种清空下,事务当然会失效。

常见的自调用导致的事务失效还有一个例子,如下:

@Servicepublic class DmzService {@Transactionalpublic void save(A a, B b) {
  saveB(b);
 }@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveB(B b){
  dao.saveB(a);
 }
}

当我们调用save方法时,我们预期的执行流程是这样的

f5b5984de34311d8d251fea82ed7fede.png

也就是说两个事务之间互不干扰,每个事务都有自己的开启、回滚、提交操作。

但根据之前的分析我们知道,实际上在调用saveB方法时,是直接调用的目标类中的saveB方法,在saveB方法前后并不会有事务的开启或者提交、回滚等操作,实际的流程是下面这样的

797fb6dcff0d6caa7904ebadbf54181e.png

由于saveB方法实际上是由dmzService也就是目标类自己调用的,所以在saveB方法的前后并不会执行事务的相关操作。这也是自调用带来问题的根本原因:「自调用时,调用的是目标类中的方法而不是代理类中的方法」

「解决方案」

  1. 自己注入自己,然后显示的调用,例如:
    @Servicepublic class DmzService {// 自己注入自己@Autowired
     DmzService dmzService;@Transactionalpublic void save(A a, B b) {
      dmzService.saveB(b);
     }@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveB(B b){
      dao.saveB(a);
     }
    }
    这种方案看起来不是很优雅利用AopContext,如下:
    @Servicepublic class DmzService {@Transactionalpublic void save(A a, B b) {
      ((DmzService) AopContext.currentProxy()).saveB(b);
     }@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveB(B b){
      dao.saveB(a);
     }
    }
    ❝使用上面这种解决方案需要注意的是,需要在配置类上新增一个配置
    // exposeProxy=true代表将代理类放入到线程上下文中,默认是false@EnableAspectJAutoProxy(exposeProxy = true)
    ❞个人比较喜欢的是第二种方式
这里我们做个来做个小总结

总结

一图胜千言
709b04b7a44697f6f9300e0e904f366e.png
事务失效的原因

事务回滚相关问题

回滚相关的问题可以被总结为两句话
  1. 想回滚的时候事务却提交了
  2. 想提交的时候被标记成只能回滚了(rollback only)
先看第一种情况:「想回滚的时候事务却提交了」。这种情况往往是程序员对Spring中事务的rollbackFor属性不够了解导致的。❝Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务;其他异常不会触发回滚事务,已经执行的SQL会提交掉。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。❞对应代码其实我们上篇文章也分析过了,如下:
7f9f0dafa62ab897bb864c536ee04b56.png
回滚代码
❝以上代码位于:TransactionAspectSupport#completeTransactionAfterThrowing方法中❞默认情况下,只有出现RuntimeException或者Error才会回滚
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}
所以,如果你想在出现了非RuntimeException或者Error时也回滚,请指定回滚时的异常,例如:
@Transactional(rollbackFor = Exception.class)
第二种情况:「想提交的时候被标记成只能回滚了(rollback only)」。对应的异常信息如下:
Transaction rolled back because it has been marked as rollback-only
我们先来看个例子吧
@Servicepublic class DmzService {@Autowired
 IndexService indexService;@Transactionalpublic void testRollbackOnly() {try {
   indexService.a();
  } catch (ClassNotFoundException e) {
   System.out.println("catch");
  }
 }
}@Servicepublic class IndexService {@Transactional(rollbackFor = Exception.class)public void a() throws ClassNotFoundException{// ......throw new ClassNotFoundException();
 }
}
在上面这个例子中,DmzServicetestRollbackOnly方法跟IndexServicea方法都开启了事务,并且事务的传播级别为required,所以当我们在testRollbackOnly中调用IndexServicea方法时这两个方法应当是共用的一个事务。按照这种思路,虽然IndexServicea方法抛出了异常,但是我们在testRollbackOnly将异常捕获了,那么这个事务应该是可以正常提交的,为什么会抛出异常呢?如果你看过我之前的源码分析的文章应该知道,在处理回滚时有这么一段代码
58b91c58d8eba8dd7b8e51eea898e2af.png
rollBackOnly设置
在提交时又做了下面这个判断(这个方法我删掉了一些不重要的代码)
eec7250835bd795c326f6d8072add256.png
commit_rollbackOnly
可以看到当提交时发现事务已经被标记为rollbackOnly后会进入回滚处理中,并且unexpected传入的为true。在处理回滚时又有下面这段代码
2532480506b4e058f6e3cb5d201bc317.png
抛出异常
最后在这里抛出了这个异常。❝以上代码均位于AbstractPlatformTransactionManager中❞总结起来,「主要的原因就是因为内部事务回滚时将整个大事务做了一个rollbackOnly的标记」,所以即使我们在外部事务中catch了抛出的异常,整个事务仍然无法正常提交,并且如果你希望正常提交,Spring还会抛出一个异常。「解决方案」:这个解决方案要依赖业务而定,你要明确你想要的结果是什么
  1. 内部事务发生异常,外部事务catch异常后,内部事务自行回滚,不影响外部事务
❝将内部事务的传播级别设置为nested/requires_new均可。在我们的例子中就是做如下修改:
// @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)public void a() throws ClassNotFoundException{// ......throw new ClassNotFoundException();
}
❞虽然这两者都能得到上面的结果,但是它们之间还是有不同的。当传播级别为requires_new时,两个事务完全没有联系,各自都有自己的事务管理机制(开启事务、关闭事务、回滚事务)。但是传播级别为nested时,实际上只存在一个事务,只是在调用a方法时设置了一个保存点,当a方法回滚时,实际上是回滚到保存点上,并且当外部事务提交时,内部事务才会提交,外部事务如果回滚,内部事务会跟着回滚。
  1. 内部事务发生异常时,外部事务catch异常后,内外两个事务都回滚,但是方法不抛出异常
@Transactionalpublic void testRollbackOnly() {try {
      indexService.a();
   } catch (ClassNotFoundException e) {// 加上这句代码
      TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
   }
}
❞通过显示的设置事务的状态为RollbackOnly。这样当提交事务时会进入下面这段代码
0cb817320963d67c7be720a01139e266.png
显示回滚
最大的区别在于处理回滚时第二个参数传入的是false,这意味着回滚是回滚是预期之中的,所以在处理完回滚后并不会抛出异常。

 

读写分离跟事务结合使用时的问题

读写分离一般有两种实现方式
  1. 配置多数据源
  2. 依赖中间件,如MyCat
如果是配置了多数据源的方式实现了读写分离,那么需要注意的是:「如果开启了一个读写事务,那么必须使用写节点」「如果是一个只读事务,那么可以使用读节点」如果是依赖于MyCat等中间件那么需要注意:「只要开启了事务,事务内的SQL都会使用写节点(依赖于具体中间件的实现,也有可能会允许使用读节点,具体策略需要自行跟DB团队确认)」基于上面的结论,我们在使用事务时应该更加谨慎,在没有必要开启事务时尽量不要开启。❝一般我们会在配置文件配置某些约定的方法名字前缀开启不同的事务(或者不开启),但现在随着注解事务的流行,好多开发人员(或者架构师)搭建框架的时候在service类上加上了@Transactional注解,导致整个类都是开启事务的,这样严重影响数据库执行的效率,更重要的是开发人员不重视、或者不知道在查询类的方法上面自己加上@Transactional(propagation=Propagation.NOT_SUPPORTED)就会导致,所有的查询方法实际并没有走从库,导致主库压力过大。❞其次,关于如果没有对只读事务做优化的话(优化意味着将只读事务路由到读节点),那么@Transactional注解中的readOnly属性就应该要慎用。我们使用readOnly的原本目的是为了将事务标记为只读,这样当MySQL服务端检测到是一个只读事务后就可以做优化,少分配一些资源(例如:只读事务不需要回滚,所以不需要分配undo log段)。但是当配置了读写分离后,可能会可能会导致只读事务内所有的SQL都被路由到了主库,读写分离也就失去了意义。

总结

本文为事务专栏最后一篇啦!这篇文章主要是总结了工作中事务相关的常见问题,想让大家少走点弯路!希望大家可以认真读完哦,有什么问题可以直接在后台私信我或者加我微信!这篇文章也是整个Spring系列的最后一篇文章,之后可能会出一篇源码阅读心得,跟大家聊聊如何学习源码。另外今年也给自己定了个小目标,就是完成SSM框架源码的阅读。目前来说Spring是完成,接下来就是SpringMVC跟MyBatis。在分析MyBatis前,会从JDBC源码出发,然后就是MyBatis对配置的解析、MyBatis执行流程、MyBatis的缓存、MyBatis的事务管理以及MyBatis的插件机制。在学习SpringMVC前,会从TomCat出发,先讲清楚TomCat的原理,我们再来看SpringMVC。整个来说相比于Spring源码,我觉得应该不算特别难。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

9d3aecd4db96effc615f5497add28678.png

好文章,我在看❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/520425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4K修复版《海上钢琴师》登陆全国院线,一文读懂背后的黑科技!

最近有件大事,11 月 15 日,全球瞩目的经典电影《海上钢琴师》4K 修复版登陆了全国院线。岁月侵蚀的影片经典场景,在老胶片的保存下只剩模糊的影像。而此次经 4K 技术修复的版本,据说采用了先进的图像超分辨率技术,分辨…

因为看见,所以发现:QBotVariant谢绝落幕

互联网给人带来便捷的同时,其公开大量的资源也同样给恶意利用者带了便捷,越来越多公开的恶意程序源码降低了对外攻击、入侵的难度,使得安全问题愈加严重。 阿里云安全团队从今年5月份监测到一BOT家族,其样本改写自互联网公开渠道…

Linux Shell脚本_历史命令显示操作时间

当前用户什么时间执行的什么命令 ① 脚本编写 创建脚本 vim displayHisoperTime.sh添加脚本内容如下: if ! grep HISTTIMEFORMAT /etc/bashrc; thenecho export HISTTIMEFORMAT"%F %T whoami " >> /etc/bashrc fi保存退出:wq ② 运行脚本 chmo…

5G套餐到底该不该换?看完你就明白啦!

戳蓝字“CSDN云计算”关注我们哦!作者 | 小枣君责编 | 阿秃此前,三大运营商正式宣布5G商用,并公布了各自的5G资费套餐。毫无意外,正式公布出来的套餐内容,和之前运营商内部泄露的内容完全一致。5G套餐公布之后&#xf…

最in的一期,来自大厂的邀请|C课有道

毋庸置疑,现在是“一切皆数”的时代,放眼目前炙手可热的大数据、人工智能等创新技术,如果没有数据加以支撑,一切的一切将毫无用武之地,更不必谈信息化、智能化and so on,而数据库作为存储和承载数据的重要场…

聚合多个文件_python数据分析与挖掘(二十五)--- Pandas高级处理分组与聚合

分组与聚合通常是分析数据的一种方式,通常与一些统计函数一起使用,查看数据的分组情况想一想其实刚才的交叉表与透视表也有分组的功能,所以算是分组的一种形式,只不过他们主要是计算次数或者计算比例!!看其…

看完这些干货帖,大数据产品从入门到精通

欢迎来到“MVP教你玩转阿里云”系列教程,在这里,你将看到各行各业数字化转型的一线实践,学到资深开发者的经验结晶。 你将以云计算领域的技术领袖为师,加速了解阿里云技术产品和各行业数字化转型的场景。 点击关注,在…

物联网现状及落地难点

大家好,我是叶帆科技的创始人兼CEO,刘洪峰,非常高兴能和阿里云MVP项目组成员一起来制作《MVP 时间》内容,帮助物联网企业开发者了解物联网接入难点及解决方案。 下面简单做个自我介绍: 1、1995年开始做软件开发&#…

物联网落地三大困境破解

大家好,我是叶帆科技创始人兼CEO,阿里云 MVP。 第一章我们讲了物联网实施以及落地的三大难点。 本章节就来看一下就这三大难点进行解析,以及将有什么样的应对方案。 在第一章节中,第一大难点说到的是接口多,协议更多…

阿里如何做到百万量级硬件故障自愈?

随着阿里大数据产品业务的增长,服务器数量不断增多,IT运维压力也成比例增大。各种软、硬件故障而造成的业务中断,成为稳定性影响的重要因素之一。本文详细解读阿里如何实现硬件故障预测、服务器自动下线、服务自愈以及集群的自平衡重建&#…

京东技术全景图首次展示 四大重磅智能技术驱动产业未来

近日主题为“突破与裂变”的2019京东全球科技探索者大会(JDDiscovery)在京盛大开幕,京东集团展示了完整的技术布局与先进而丰富的对外技术服务,对外明确诠释了“以零售为基础的技术与服务企业”的集团战略定位。 智能供应链国家人…

9008刷机怎么刷_手机刷机怎么刷

有人说玩安卓机不刷机就体验不到真正的乐趣,那么手机怎么刷机呢?下面小编会介绍方法给想刷机的朋友,一起来看看吧。手机刷机怎么刷步骤1:手机安装一款刷机软件【卓大师】,很多刷机软件都有一键ROOT的功能,刷…

阿里工业互联网平台“思考”:一场从0到1的蜕变

阿里云总裁胡晓明(花名孙权)曾在一场媒体采访中透露了自己的业务秘籍,说他永远记住两句话,一是“拓展商业边界”,二是“商业驱动技术进步”。 这句话不失为阿里的真实写照。的确,企业的安身立命之道&#…

JSON数据从MongoDB迁移到MaxCompute最佳实践

数据及账号准备 首先您需要将数据上传至您的MongoDB数据库。本例中使用阿里云的云数据库 MongoDB 版,网络类型为VPC(需申请公网地址,否则无法与DataWorks默认资源组互通),测试数据如下。 {"store": {"…

SpringBoot 整合 Shiro thymeleaf _01_Shiro概念

文章目录shiro简介以及功能描述shiro实现原理理解shiro的架构理解shiro认证功能(Authentication)流程Realmshiro 拦截器规则shiro简介以及功能描述 Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相…

中正则表达式详解_python :正则表达式/re库 超级详细de注释解释

首先要解释,什么是正则表达式:给定一串字符串,我们可以提取他的特征,把他抽象成一个类型。 下次遇到和这个字符串同类的字符串,我们只需要判断是否符合特征,就知道这个字符串是不是同类的。举个例子&#x…

打破多项世界记录  双11背后最大的力量是技术

双11是一次全球商业奇迹,同时也是一次技术奇迹。正是诸多世界顶尖的技术,助力双11一次又一次打破成交纪录,形成了独一无二的世界奇观。 阿里巴巴核心系统100%上云、每秒订单创建峰值54.4万笔、自研数据库POLARDB和OceanBase分别处理8700万、6…

阿里巴巴下一代云分析型数据库AnalyticDB入选Forrester Wave™ 云数仓评估报告 解读

前言 近期, 全球权威IT咨询机构Forrester发布"The Forrester WaveTM: CloudData Warehouse Q4 2018"研究报告,阿里巴巴分析型数据库(AnalyticDB)成功入选 !AnalyticDB作为阿里巴巴自主研发的PB级实时云数据仓库,全面兼容MySQL协议以…

广州云栖大会:阿里云携手虎牙,首次落地直播行业边缘节点及云企业网服务

2018年11月22日,由阿里巴巴集团主办的广东省大数据开发者大会暨2018广东云栖大会在广州正式召开,其中以助力游戏企业畅游全球为主题的游戏云专场也在上午如期举办。 在游戏云专场中,阿里云高级商务专家李知明首先发言:边缘计算节点…