乐趣区

关于前端: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
多线程调用 办法在不同线程中,数据库链接有可能不是一个,从而是两个不同事务
退出移动版