@Transaction注解的失效场景
作者:京东物流 孔祥东
背景
事情是这样,最近在实现一个需求的时候,有一个定时异步任务会捞取主表的数据并置为处理中(为了防止任务执行时间过长,下次任务执行把本次数据重复捞取),然后根据主表关联明细表数据,然后将明细表数据进行组装,等待所有明细数据处理完成之后,将主表状态置为完成;大概当时的代码示例(只是截取部分)如下:
@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 |
多线程调用 | 方法在不同线程中,数据库链接有可能不是一个,从而是两个不同事务 |

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
JRC Flink流作业调优指南
作者:京东物流 康琪 本文综合Apache Flink原理与京东实时计算平台(JRC)的背景,详细讲述了大规模Flink流作业的调优方法。通过阅读本文,读者可了解Flink流作业的通用调优措施,并应用于生产环境。 写在前面 Apache Flink作为Google Dataflow Model的工业级实现,经过多年的发展,如今已经成为流式计算开源领域的事实标准。它具有高吞吐、低时延、原生流批一体、高一致性、高可用性、高伸缩性的特征,同时提供丰富的层级化API、时间窗口、状态化计算等语义,方便用户快速入门实时开发,构建实时计算体系。 古语有云,工欲善其事,必先利其器。要想让大规模、大流量的Flink作业高效运行,就必然要进行调优,并且理解其背后的原理。本文是笔者根据过往经验以及调优实践,结合京东实时计算平台(JRC)背景产出的面向专业人员的Flink流作业调优指南。主要包含以下四个方面: TaskManager内存模型调优 网络栈调优 RocksDB与状态调优 其他调优项 本文基于Flink 1.12版本。阅读之前,建议读者对Flink基础组件、编程模型和运行时有较深入的了解。 01 T...
- 下一篇
如何高效实现 MySQL 与 elasticsearch 的数据同步
MySQL 自身简单、高效、可靠,是又拍云内部使用最广泛的数据库。但是当数据量达到一定程度的时候,对整个 MySQL 的操作会变得非常迟缓。而公司内部 robin/logs 表的数据量已经达到 800w,后续又有全文检索的需求。这个需求直接在 MySQL 上实施是难以做到的。 原数据库的同步问题 由于传统的 mysql 数据库并不擅长海量数据的检索,当数据量到达一定规模时(估算单表两千万左右),查询和插入的耗时会明显增加。同样,当需要对这些数据进行模糊查询或是数据分析时,MySQL作为事务型关系数据库很难提供良好的性能支持。使用适合的数据库来实现模糊查询是解决这个问题的关键。 但是,切换数据库会迎来两个问题,一是已有的服务对现在的 MySQL 重度依赖,二是 MySQL 的事务能力和软件生态仍然不可替代,直接迁移数据库的成本过大。我们综合考虑了下,决定同时使用多个数据库的方案,不同的数据库应用于不同的使用场景。而在支持模糊查询功能的数据库中,elasticsearch 自然是首选的查询数据库。这样后续对业务需求的切换也会非常灵活。 那具体该如何实现呢?在又拍云以往的项目中,也有遇到相似...
相关文章
文章评论
共有0条评论来说两句吧...