Spring事务的处理流程、传播属性、及部分释疑

目录


spring或是AOP参考: spring概要

一、spring事务利用AOP的拦截和处理流程


如果知道了事务的处理流程,去理解事务传播属性导致的回滚就是分分钟的事儿了。

想要知道事务的拦截处理流程,只需要分析TransactionAspectSupport类的部分源码即可

// 事务拦截处理方法
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
        final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
        final String joinpointIdentification = this.methodIdentification(method, targetClass);
        // 以下代码只是部分代码,为了方便看只截取了能说明问题的部分
	      // 1.获取事务
          TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
           Object retVal = null;

           try {
	            // 2.处理真正调用的方法
               retVal = invocation.proceedWithInvocation();
           } catch (Throwable var15) {
	            // 3.有异常回滚:
               this.completeTransactionAfterThrowing(txInfo, var15);
               throw var15;
           } finally {
               this.cleanupTransactionInfo(txInfo);
           }
			// 4.处理方法完成,提交事务
           this.commitTransactionAfterReturning(txInfo);
           return retVal;
        
    }

从上面注释可以看到,任何一个被事务拦截的方法,都是先在真正调用该方法之前获取了事务,执行完该方法后再决定是事务回滚或提交。

我们还可以看到,调用处理真正要执行的方法是被try catch的,而catch块里才会有事务回滚的代码。所以,如果某个事务方法A内部有异常没有抛出来(被自己的try cathch块捕获了),而不能被这里的try catch块捕获到,那么这个方法A就不会被回滚。
不会回滚的例子

   @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public void saveTx() {
        try {
            Log log = new Log();
            log.setTitle("saveTx");
            log.setBeginDate(new Date());
            logService.save(log);
            int i=0;
            int j=2/i;
        }catch (Exception e){
            e.printStackTrace();
        }

    }

将会回滚的例子

   @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    @Override
    public void saveTx() {        
	    Log log = new Log();
        log.setTitle("saveTx");
        log.setBeginDate(new Date());
        logService.save(log);
        int i=0;
        int j=2/i;      
    }

在我们写代码的时候,有时候会考虑某个方法是否应该被回滚,那么我们就得注意方法里的异常是否该被try catch是非常重要的。

还有一点,如果一个事务方法调用了一个非事务方法,那么非事务方法就要看作是事务方法的一部分,它们共用的一个数据库连接。

必须记住的两个要点
  1. 事务拦截处理方法中,只要执行到catch块里的事务回滚代码才会使事务回滚【理解什么时候回滚的关键】。
  2. 同一个事务里,任何地方导致执行了事务拦截方法里的回滚代码,就会导致整个事务回滚【共用一个DB Conncetion】。

二、事务传播属性


表格中只要说到加入父事务后,就为同一个事务,他们会共用一个Connection

传播属性说明
required如果当前存在事务则加入该事务,如果没有则新建一个事务
supports如果当前存在事务则加入该事务,如果没有则以非事务的方式运行。
mandatory强制加入当前的事务。如果当前没有事务,就抛出异常。
requires_new新建事务。如果当前存在事务,把当前事务挂起。
not_supported不支持事务。如果当前存在事务,就把当前事务挂起,然后以非事务方式执行。
never以非事务方式执行,如果当前存在事务,则抛出异常。
nested如果当前存在事务,则嵌套执行(相当于指定了回滚点SavePoint)。如果当前没有事务,则新建事务。
**nested** 时,如果嵌套事务发生异常回滚,如果父事务捕获了异常,则父事务是不会回滚的,因为它只会回滚到指定的回滚点。

三、为什么很多Exception异常必须配置在rollback-for中才有用


经历过的都知道,Exception异常如果不在rollback-for属性当中指定,即使出现了Exception异常也不会发生事务回滚。

这是因为spring事务处理时,只对RuntimeException和Error异常进行了处理,而Exception没有在其中。

我们看看TransactionAspectSupport类回滚方法里的代码:下面代码只看注释就行了

protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {
        if (txInfo != null && txInfo.hasTransaction()) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);
            }
			// 判断是否是要回滚的异常:rollbackOn方法代码在后面
            if (txInfo.transactionAttribute.rollbackOn(ex)) {
                try {
	                // 回滚
                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                } catch (TransactionSystemException var7) {
                    this.logger.error("Application exception overridden by rollback exception", ex);
                    var7.initApplicationException(ex);
                    throw var7;
                } catch (RuntimeException var8) {
                    this.logger.error("Application exception overridden by rollback exception", ex);
                    throw var8;
                } catch (Error var9) {
                    this.logger.error("Application exception overridden by rollback error", ex);
                    throw var9;
                }
                
             // 不在指定异常范围内
            } else {
                try {
	                // 事务提交 
                    txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                } catch (TransactionSystemException var4) {
                    this.logger.error("Application exception overridden by commit exception", ex);
                    var4.initApplicationException(ex);
                    throw var4;
                } catch (RuntimeException var5) {
                    this.logger.error("Application exception overridden by commit exception", ex);
                    throw var5;
                } catch (Error var6) {
                    this.logger.error("Application exception overridden by commit error", ex);
                    throw var6;
                }
            }
        }

    }

异常判断方法:

// RuleBasedTransactionAttribute类的方法
public boolean rollbackOn(Throwable ex) {
        if (logger.isTraceEnabled()) {
            logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
        }

        RollbackRuleAttribute winner = null;
        int deepest = 2147483647;
        if (this.rollbackRules != null) {
            Iterator var4 = this.rollbackRules.iterator();
			// 遍历rollback规则
            while(var4.hasNext()) {
                RollbackRuleAttribute rule = (RollbackRuleAttribute)var4.next();
                // 查找当前异常是否在规则里面
                int depth = rule.getDepth(ex);
                if (depth >= 0 && depth < deepest) {
                    deepest = depth;
                    winner = rule;
                }
            }
        }

        if (logger.isTraceEnabled()) {
            logger.trace("Winning rollback rule is: " + winner);
        }
		// 该异常没在rollback规则里,则再去判断是否是RuntimeException或Error
        if (winner == null) {
            logger.trace("No relevant rollback rule found: applying default rules");
            // 这里调用的方法参考
            return super.rollbackOn(ex);
        } else {
	        // rollback规则里有此异常就返true,即要回滚
            return !(winner instanceof NoRollbackRuleAttribute);
        }
    }

代码片段3:

  // 如果是RuntimeException或Error异常的子类就返回true
  public boolean rollbackOn(Throwable ex) {
		// 只指定了这两个异常,没有Exception异常
        return ex instanceof RuntimeException || ex instanceof Error;
    }

rollback-for属性只针对Error和RuntimeException

我在网上找了一个特别合适的类图来说明:
这里写图片描述

四、事务的传播性在同一个类中方法互调时为什么会失效?


我们知道,spring的事务都是通过AOP动态代理实现的。如果想要任何一个方法实现事务代理,就必须通过事务代理类去调用,而内部方法调用与事务代理类一点我关系都木有,所以不会生效。下面我举个例子

要代理的接口类

public interface Subject {
    void methodOne();
    void methodTwo();
}

要代理的真实类

public class RealSubject implements Subject {
    @Override
    public void methodOne() {
        System.out.println("one");
        this.menthoTwo();
    }

    @Override
    public void methodTwo() {
        System.out.println("two");
    }
}

代理类要调用的事务处理器(代理类就是通过它对方法实现的额外处理)

public class MyInvocationHandler implements InvocationHandler {
	
    private Object obj;
    public MyInvocationHandler(){

    }
    public MyInvocationHandler(Object obj){
        this.obj = obj;
    }
    // 调用方法前的额外处理:记录方法调用开始时间
    public void startRecordRequestTime(){
        System.out.println("方法调用开始时间:"+System.currentTimeMillis());
    }
    // 调用方法前的额外处理:模拟处理事务的传播属性
    public void processTransaction(){
        System.out.println("事务处理..........");
    }
    // 调用方法后的额外处理:记录方法调用结束时间
    public void endRecordRequestTime(){
        System.out.println("方法调用结束时间:"+System.currentTimeMillis());
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.startRecordRequestTime();
        // 调用处理事务传播属性方法
        this.processTransaction();
        // 通过反射执行真实的方法
        method.invoke(obj,args);
        this.endRecordRequestTime();
        return null;
    }
}

使用代理访问

public class Test {  
    public static void main(String[] args) {
        Subject sub = new RealSubject();
        MyInvocationHandler handler = new MyInvocationHandler(sub);
        // 利用反射动态生成的代理类
        Subject proxy=(Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},handler);
	    // 通过代理类访问方法
        proxy.methodOne();
		
    }
}

//运行结果为:
方法调用开始时间:1531564567403
事务处理..........
one
two
方法调用结束时间:1531564567403
.

好了,从执行了proxy.methodOne();这句代码后的结果能看到,当代理类调用了methodOne()后,methodTwo()是在内部调用的,与代理类一点儿关系也没有,所以methodOne调用内部的methodTwo时,是不会走事务处理代码的,所以事务传播属性也就会失效。
上面动态生成的代理类大概如下,有兴趣的可以了解下,重点是下面的重写方法调用的MyInvocationHandler 类的invoke方法:

public class $Proxy0 extends Proxy implements Subject {
    private static Method m0;
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",
                    new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals",
                    new Class[] { Class.forName("java.lang.Object") });
            m2 = Class.forName("java.lang.Object").getMethod("toString",
                    new Class[0]);
            // 方法1
            m3 = Class.forName("***.RealSubject").getMethod("methodOne",
                    new Class[0]);
            // 方法2
            m4 = Class.forName("***.RealSubject").getMethod("methodTwo",
                    new Class[0]);


        } catch (NoSuchMethodException nosuchmethodexception) {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        } catch (ClassNotFoundException classnotfoundexception) {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }

    public $Proxy0(InvocationHandler invocationhandler) {
        super(invocationhandler);
    }
    
    @Override
    public final int hashCode() {
        try {
            return ((Integer) super.h.invoke(this, m0, null)).intValue();
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    @Override
    public final String toString() {
        try {
            return (String) super.h.invoke(this, m2, null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }


    // 重写Subject接口方法一
    @Override
    public final  void methodOne() {
        try {
            // 调用事务增强处理方法
            super.h.invoke(this,m3,null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
    // 重写Subject接口方法二
    @Override
    public final  void methodTwo() {
        try {
            // 调用事务增强处理方法
            super.h.invoke(this,m4,null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值