作者:京东物流 孔祥东

背景

事情是这样,最近在实现一个需求的时候,有一个定时异步任务会捞取主表的数据并置为处理中(为了防止任务执行时间过长,下次任务执行把本次数据重复捞取),然后根据主表关联明细表数据,然后将明细表数据进行组装,等待所有明细数据处理完成之后,将主表状态置为完成;大概当时的代码示例(只是截取部分)如下:

    @Override
@Transactional
protected void executeTasks(List<AbnormalHotspot> list) {
CallerInfo infoJk = Profiler.registerInfo("com.jd.xxxxx.executeTasks", "qc-xxxxxx",false, true); try{
//更新主表的状态为中间态
hotSpotService.updateAbnormalHotspotStatus(list, HotSpotStatusEnum.EXECUTING.getCode()); //处理明细表数据
for(AbnormalHotspot hotspot : list){ //组装批次基本信息
AbnormalHotSpotSendToMcssMq spotSendToMcssMq = assemblyAbnormalHotSpotSendToMcssMqFromMain(hotspot); //组装附件信息,此处存在抛出IOException 异常的可能
List<HotSpotAttachmentBo> attachmentBos = assemblyAttachment(hotspot.getBusinessCode());
spotSendToMcssMq.setAttachmentAddr(JSON.toJSONString(attachmentBos)); }
//更新主表的状态为终态
hotSpotService.updateAbnormalHotspotStatus(list, HotSpotStatusEnum.FINISHED.getCode()); }finally {
Profiler.registerInfoEnd(infoJk);
}

然后执行测试的时候发现,代码抛出异常了,可主表数据的状态一直是处理中,并没有发生回滚,但是看代码也已经加上@Transaction 注解了,所以就怀疑是不是事务没有生效,带着这个问题就顺便重新复习了一下@Transaction 注解的使用以及事务相关的一些知识。

过程

首先带着刚刚的问题,来看看Spring 的源码。

/**  @Transaction 注解中的这个方法定义,可以指定回滚的异常类型,
可以指定0-多个exception 子类
* Defines zero (0) or more exception {@link Class classes}, which must be a
* subclass of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
*/ Class<? extends Throwable>[] rollbackFor() default {}

接着再看

org.springframework.transaction.interceptor.RollbackRuleAttribute类中有一个方法是在匹配查找异常。

/**
* 递归查询匹配的异常类
* Return the depth of the superclass matching.
* <p>{@code 0} means {@code ex} matches exactly. Returns
* {@code -1} if there is no match. Otherwise, returns depth with the
* lowest depth winning.
*/
public int getDepth(Throwable ex) {
return getDepth(ex.getClass(), 0);
} 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.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

这时候再看这个getDepth 方法的调用的地方是这个

org.springframework.transaction.interceptor.RuleBasedTransactionAttribute 类,这个类中就会出现一个rollbackOn 的方法,但是这个方法并不是它自身的,而且重写了它的父类org.springframework.transaction.interceptor.DefaultTransactionAttribute,所以我们需要看的是这个默认的实物属性类的描述。

/**  默认的回滚行为 unchecked exception,并且ERROR 也会回滚
* The default behavior is as with EJB: rollback on unchecked exception.
* Additionally attempt to rollback on Error.
* <p>This is consistent with TransactionTemplate's default behavior.
*/
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}

到这里我们应该就可以知道上述问题的缘故了。

结论

@Transaction 如果不显示声明回滚的异常类型的话,默认只会回滚RuntimeException 异常(运行时异常)及其子类以及Error 及其子类,由此也可以得出,如果事务方法中的异常被catch 了,也会使事务失效。

扩展总结

到这里,你以为就完了吗!这就一点不符合我们的程序员的发型了!!!!

下面,我们就来看一下@Transaction 里面是什么东西

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional { @AliasFor("transactionManager")
String value() default "";
//事务管理器名称
@AliasFor("value")
String transactionManager() default "";
//事务传播模式
Propagation propagation() default Propagation.REQUIRED;
//事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
//超时时间
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
//是否是只读事务
boolean readOnly() default false;
//需要回滚的异常类
Class<? extends Throwable>[] rollbackFor() default {};
//需要回滚的异常类名称
String[] rollbackForClassName() default {};
//排除回滚的异常类
Class<? extends Throwable>[] noRollbackFor() default {};
//排除回滚的异常类名称
String[] noRollbackForClassName() default {};
}

value,transactionManager 方法都是设置事务管理器的,不太需要关注

propagation 事务传播行为

为了解决业务层方法之间互相调用的事务问题。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

public enum Propagation {

	//默认值
//当前有事务,就加入这个事务,没有事务,就新建一个事务(也就是说如果A方法和B方法都添加了注解,默认传播模式下,A方法调用B方法,会将两个方法事务合并为一个)
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), //当前有事务,就加入这个事务,没有事务,就以非事务的方式执行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), //当前有事务,就加入这个事务,没有事务,就抛出异常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), //新建一个事务执行,如果当前有事务,就把当前的事务挂起(如果A方法默认为Propagation.REQUIRED模式,B方法为Propagation.REQUIRES_NEW,在A方法中调用B方法,A方法抛出异常后,B方法不会回滚,因为Propagation.REQUIRES_NEW会暂停A方法的事务)
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), //在无事务状态下执行,如果当前有事务,就把当前的事务挂起
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), //在无事务状态下执行,如果当前有事务,会抛出异常
NEVER(TransactionDefinition.PROPAGATION_NEVER), //当前有事务,就新建一个事务,嵌套执行,当前无事务,就新建一个事务执行(Spring 特有的)
NESTED(TransactionDefinition.PROPAGATION_NESTE

看到这里就会发现,如果事务传播行为设置不当的话,也会使事务失效。

从上述来看,配置错误这三种

TransactionDefinition.PROPAGATION_SUPPORTS,TransactionDefinition.PROPAGATION_NOT_SUPPORTED,TransactionDefinition.PROPAGATION_NEVER都有可能会出现失效。

isolation 方法

定义了一个事务可能受其他并发事务影响的程度,带来的是脏读,丢失修改,不可重复读,幻读等问题,所以不会是事务失效,这部分内容还可以进一步研究。

timeout

定义的是事务的最长执行时间,如果超过该时间限制但事务还没有完成,则自动回滚事务,也不会使事务失效。

readOnly:

事务的只读属性是指,对事务性资源进行只读操作或者是读写操作.

rollbackfor,rollbackforClassName,norollbackfor,rollbackforClassName

都是显示声明哪些异常类需要回滚或者不需要回滚,这个在上述已经回答过了。

看到这里,是不是以为失效的场景就这些了呢,No

再进一步想想,Spring 事务是基于什么实现的?是不是每个程序员在学习AOP 的时候都会听到AOP 的应用场景,如日志,事务,权限等等。

所以想想,既然Spring 事务是基于AOP 实现的,那可以想想如果事务方法要是没有被Spring 代理对象来调用的话,是不是就加不上事务了,打个比方,如下代码:

class TransactionTest{
public void A() throws Exception {
this.B();
... ...
} @Transactional()
public void B() throws Exception {
//数据源操作
}
}

方法B 的事务会生效吗?答案是不会,因为this 是指当前实例,并不是Spring 代理的,所以B 方法的事务肯定是加不上的,由此可以得出,在同一个类中方法调用也会使事务失效。

其实上述提到的事务时效只是基于自己的遇到的问题来分析,对于Spring 事务时效的场景应该来说还有很多很多,下面大概整理一下常见的吧。

失效场景 备注
未加入Spring容器管理 类未标注@Service、@Component等注解,或者Spring扫描路径不对
表不支持 mysql5 之前数据库引擎默认是myisam ,它是不支持事务的
catch 异常 将增删改方法catch了
自定义回滚异常 默认只能回滚RuntimeException 异常,如果自定义了一个回滚异常,但是实际抛出的异常又不是声明的自定义的,就会时效
抛出了自定义异常 默认只能回滚RuntimeException 异常,如果自定义了一个抛出异常,又没有在注解中显示声明对应回滚异常
错误的传播特性 上述已讲解
方法内部调用 一个类里面的方法,相互调用
方法使用final 修改 方法使用final 修饰
方法访问权限不对 方法声明成private
多线程调用 方法在不同线程中,数据库链接有可能不是一个,从而是两个不同事务

@Transaction注解的失效场景的更多相关文章

  1. @Transactional注解的失效场景

    一口气说出 6种,@Transactional注解的失效场景 计算机java编程 发布时间: 20-03-1912:35优质科技领域创作者 引言 昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Tr ...

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

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...

  3. @Transaction注解失效的几种场景

    一.@Transactional介绍 1.@Transactional注解可以作用于哪些地方? @Transactional 可以作用在接口.类.类方法上. 作用于类:表示所有该类的public方法都 ...

  4. Spring中@Translational注解失效场景

    今天面试被问到@Translational注解什么场景下失效,我一脸懵逼,说的恍恍惚惚的,下来我就总结一下@Translational注解失效的场景! @Transactional 注解相信大家并不陌 ...

  5. @Transactional 注解失效场景

    @Transactional可以用在接口.类.类方法上. 作用于类:当把@Transactional注解放在类上时,表示该类的所有public方法都配置了该事物注解. 作用于方法:表示该方法配置了事物 ...

  6. Spring @Transaction 注解是如何执行事务的?

    前言 相信小伙伴一定用过 @Transaction 注解,那 @Transaction 背后的秘密又知道多少呢? Spring 是如何开启事务的?又是如何进行提交事务和关闭事务的呢? 画图猜测 在开始 ...

  7. spring + mybatis 注解 @Transactional失效

    1.问题 在使用@Transactional注解管理事务的时候会出现很多错误,比如: *** was not registered for synchronization because synchr ...

  8. 《MySQL面试小抄》索引失效场景验证

    我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统,自己无法快速定位到关键问题点!!! 本期主要面试考点 面试官考点之什么情况下会索 ...

  9. @Transactional注解事务失效的几种场景及原因

    1. 介紹 在业务开发的许多场景中,我们会使用到通过事务去控制多个操作的一致性.比较多的就是通过声明式事务,即使用 @Transactional 注解修饰方法的形式.但在使用过程中,要足够了解事务失效 ...

  10. transaction注解分析

    1. Spring事务的基本原理 事务管理是应用系统开发中必不可少的一部分.Spring 为事务管理提供了丰富的功能支持.Spring 事务管理分为编码式和声明式的两种方式.编程式事务指的是通过编码方 ...

随机推荐

  1. ModelArts的雪中送炭,让我拿下CCF BDCI华为Severless工作负载预测亚军

    摘要: 中国计算机学会大数据与计算智能大赛(CCF BDCI)华为Severless工作负载预测亚军方案和ModelArts使用体验分享 本文分享自华为云社区<免费薅ModelArts算力资源- ...

  2. 智能电视APP鲜时光,如何应用AB测试打造极致的用户观看体验?

     更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群   数字技术的发展让智能电视普及率大幅提升,2023年智能电视的市场渗透率已超90%,与智能电视相匹配的各类应用 ...

  3. 火山引擎 DataLeap:3 小时分享,体系化讲透企业数据治理如何做?

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 数据治理伴随着数据全生命周期的进程,涉及事前规范检查.事中监控管理.事后优化复盘等过程,关键重点领域包括数据质量的 ...

  4. .Net Core 开发框架,支持多版本的类库

    工具:Visual Studio 2019 1.新建一个 .NET Standard 类库. 2.填写项目名称 3.编辑项目文件 可以看到当前类库默认为 netstandard2.0,而此时其xml标 ...

  5. Java 日志框架学习笔记

    日志概念 1. 日志文件 日志文件是用于记录系统操作事件的文件集合 1.1 调试日志 1.2 系统日志 系统日志是记录系统中硬件.软件和系统问题的信息,同时还可以监视系统中发生的事件.用户可以通过它来 ...

  6. CO40/CO41转生产订单下达时不能创建采购申请

    一.配置 CO01创建生产订单,创建时生成采购申请,改为下达时创建采购申请.通过配置,将预留/采购申请 更改为2即可. 但是CO41和CO40通过配置,并不能达到更改预留/采购申请 为2. 二.调试源 ...

  7. Make、Makefile、Cmake、QMake 的区别

    本博文的简述or解决问题? make makefile cmake qmake都是什么,有什么区别? 查了一下好像是编译用的,既然是编译为什么我们不用g++. javac 来编译呢?我猜答案是方便一点 ...

  8. 三、springboot集成金仓数据库

    系列导航 一.金仓数据库搭建(单机) 二.金仓数据库搭建(主从安装) 三.springboot集成金仓数据库 1.数据库中创建表 CREATE TABLE TEST_BLOCK_T ( BLOCK_I ...

  9. 【转载】内存基本概念-伙伴(Buddy)算法

    简介 ​ 在Linux系统中,内存的分配与回收速率直接影响系统的存取效率.当内核频繁请求和释放不同大小的一组连续页框时,会导致许多外部空闲碎片,造成空间的浪费.使用伙伴算法可以有效地缓解该问题.伙伴关 ...

  10. 如何一键私有化部署 Laf ?

    太长不看:Laf 上架了 Sealos 的模板市场,通过 Laf 应用模板即可一键部署! Laf 是一个完全开源的项目,除了使用公有云之外,还有大量的用户选择私有化部署 Laf.然而,私有化部署通常伴 ...