当前位置: 首页 > Web前端 > HTML

@Transaction注解的失败场景

时间:2023-03-28 18:49:08 HTML

作者:孔向东,京东物流后台的东西是这样的。最近在实现一个需求的时候,有一个定时异步任务,会取主表的数据,并设置为处理中(为了防止任务执行时间过长,下次任务执行时会重复取这个数据),然后根据主表关联明细表数据,然后组装明细表数据,等待所有明细数据处理完毕,然后设置主表状态为完成;大概当时的代码示例(只是拦截部分)如下:qc-xxxxxx",假,真);try{//更新主表状态为中间状态hotSpotService.updateAbnormalHotspotStatus(list,HotSpotStatusEnum.EXECUTING.getCode());//处理明细表数据for(AbnormalHotspothotspot:list){//组装批次AbnormalHotSpotSendToMcssMq的基本信息spotSendToMcssMq=assemblyAbnormalHotSpotSendToMcssMqFromMain(hotspot);//程序集附件信息,这里有可能抛出IOExceptionListattachmentBos=assemblyAttachment(hotspot.getBusinessCode());点发送.setToMcssAttachmentAddr(JSON.toJSONString(attachmentBos));}//更新主表状态为最终状态hotSpotService.updateAbnormalHotspotStatus(list,HotSpotStatusEnum.FINISHED.getCode());}最后{Profiler.registerInfoEnd(infoJk);然后执行测试的时候发现代码抛出了异常,但是主表数据的状态一直在处理,没有发生回滚,但是代码也被@Transaction注解了,所以想知道是不是transaction没有生效,with对于这个问题,顺便重温了一下@Transaction注解的使用和一些与transaction相关的知识流程。首先我们带着刚才的问题来看一下Spring的源码。/**@Transaction注解中的方法定义可以指定回滚的异常类型,可以指定0-多个异常子类*定义零(0)个或多个异常{@linkClassclasses},必须是*的子类{@linkThrowable},指示哪些异常类型必须导致*事务回滚。*

这是构造回滚规则的首选方式,匹配异常类和子类。*

类似于{@linkorg.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Classclazz)}*/Class[]rollbackFor()default{}然后看org.springframework.transaction.interceptor.RollbackRuleAttribute类中的一个方法是在匹配中寻找异常。/***递归查询匹配异常类*返回超类匹配的深度。*

{@code0}表示{@codeex}完全匹配。如果没有匹配项,则返回*{@code-1}。否则,返回以*最低深度获胜的深度。*/publicintgetDepth(Throwableex){returngetDepth(ex.getClass(),0);}privateintgetDepth(ClassexceptionClass,intdepth){if(exceptionClass.getName().contains(this.exceptionName)){//找到了!返回深度;}//如果我们已经尽我们所能,但还没有找到它...if(exceptionClass.equals(Throwable.class)){return-1;}返回getDepth(exceptionClass.getSuperclass(),depth+1);这个时候调用getDepth方法的地方就是这个org.springframework.transaction.interceptor.RuleBasedTransactionAttribute类,这个类里面会有一个rollbackOn方法,但是这个方法不是自己的,重写了它的父类org.springframework.transaction.interceptor.DefaultTransactionAttribute,所以我们要看的是这个默认物理属性class的描述/**默认的回滚行为是uncheckedexception,ERROR也会回滚*默认的行为和EJB一样:rollbackonuncheckedexception。*额外尝试回滚错误。*

这与TransactionTemplate的默认行为一致。*/publicbooleanrollbackOn(Throwableex){return(exinstanceofRuntimeException||exinstanceofError);为了。结论如果@Transaction不显示声明回滚的异常类型,默认只会回滚RuntimeException(运行时异常)及其子类和Error及其子类。由此也可以得出结论,如果事务方法中的异常被捕获,也会使事务失效。扩展就总结到这里了,是不是觉得就结束了!这根本不适合我们程序员的发型!!!!接下来我们看一下@Transaction@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceTransactional{@AliasFor("transactionManager")Stringvalue()默认””;//事务管理器名称@AliasFor("value")StringtransactionManager()default"";//事务传播方式Propagationpropagation()defaultPropagation.REQUIRED;//事务隔离级别Isolationisolation()defaultIsolation.DEFAULT;//超时时间inttimeout()defaultTransactionDefinition.TIMEOUT_DEFAULT;//是否为只读事务booleanreadOnly()defaultfalse;//需要回滚的异常类Class[]rollbackFor()默认{};//需要回滚的异常类名称String[]rollbackForClassName()default{};//排除回滚的异常类Class[]noRollbackFor()默认{};//排除回滚异常类名String[]noRollbackForClassName()default{};}value,transactionManager方法是就是设置事务管理器,所以不需要关注propagation事务传播行为。为了解决业务层方法之间相互调用的事务问题,当一个事务方法被另一个事务方法调用时,需要指定事务应该如何传播。例如:一个方法可能会继续运行在一个已经存在的事务中,也可能会启动一个新的事务并运行在它自己的事务中。TransactionDefinition定义中包含以下代表传播行为的常量:publicenumPropagation{//默认值//如果当前有交易,则加入这个交易,如果没有交易,则创建一个新交易(即如果Amethod和B方法中添加注解,在默认传播模式下,当方法A调用方法B时,两个方法的事务会合并为一个)以非事务方式执行SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),//如果当前有事务,则加入这个事务,如果没有事务,则抛出异常MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),//新建一个事务执行,如果当前有事务,则暂停当前事务(如果方法A默认为Propagation.REQUIRED模式,方法B为Propagation.REQUIRES_NEW,在方法A中调用方法B,方法A抛出异常后,方法B不会roll回来,因为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.PROPAGATION\_NOT\_SUPPORTED、TransactionDefinition.PROPAGATION\_NEVER这三种类型可能因为配置错误而失败。隔离方式定义了一个事务在多大程度上可能受到其他并发事务的影响,导致脏读、丢失修改、不可重复读、幻读等,所以不会是事务失败,这部分可以有待进一步研究。超时定义事务的最大执行时间。如果超过时间限制但交易还没有完成,交易会自动回滚,交易不会作废。readOnly:事务的只读属性,是指对事务资源的只读操作或读写操作。rollbackfor,rollbackforClassName,norollbackfor,rollbackforClassName都显示和声明哪些异常类需要回滚或者不需要回滚。这个上面已经回答过了。看到这里,是不是觉得失败的场景都有了呢?不对,再想一想,Spring的事务是基于什么的呢?是不是每个程序员在学习AOP的时候都听说过AOP的应用场景,比如日志、事务、权限等等。所以想一想,既然Spring事务是基于AOP实现的,那么你可以想一下,如果事务方法不是由Spring代理对象调用的,是不是就不能添加事务了,例如下面的代码:classTransactionTest{publicvoidA()抛出异常{this.B();……}@Transactional()publicvoidB()throwsException{//数据源操作}}方法B的事务会生效吗?答案是否定的,因为this指的是当前实例,不是Spring代理,所以方法B的事务一定不能加。由此可以得出结论,同一个类中的方法调用也会使事务失效。其实上面说的交易时效只是根据自己遇到的问题来分析的。Spring事务时效性应该有很多场景。下面我们来梳理一下常见的。Spring容器管理类没有添加失效场景的备注和@Service、@Component等注解没有标注,或者Spring扫描路径不对,表不支持mysql5。默认的数据库引擎是myisam,不支持事务捕获异常。自动捕获增删改查定义的回滚异常,默认只能回滚RuntimeException异常。如果自定义了回滚异常,但实际抛出的异常不是声明的自定义异常,则自定义异常会超时抛出。默认情况下,只有RuntimeException可以回滚。Exception,如果你自定义了一个抛出异常,并且没有在注解中显示对应回滚异常错误的传播特性权限是错误的。该方法被声明为私有的。多线程调用方法在不同的线程中。数据库连接可能不是一个,所以是两个不同的事务。