彻底弄懂spring事务源码以及使用

一背景:先看如下代码,然后思考问题

@Service
public class MyService {

    public void doTest(){
        this.doTest2();
    }
    @Transactional
    public void doTest2(){
        this.doTest3();

    }

    @Transactional(timeout = 20)
    public void doTest3(){
        this.doTest4();
    }
    @Transactional(timeout = 30,propagation=Propagation.REQUIRED)
    public void doTest4(){
        System.out.println("ddddddddddddddddd");

    }
}

@Configuration
@ComponentScan("com.yang.xiao.hui.aop")
@EnableTransactionManagement
public class App {
    public static void main(String[] args) {

        ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
        MyService service = ctx.getBean(MyService.class);
        service.doTest();
        service.doTest2();
    }
}

思考1:如果我接着调用doTest()方法,事务会生效么?

思考2:如果我调用doTest2(),那么事务是以doTest2的为准,还是doTest3的为准,还是doTest4的为准

思考3:贴了@Transactional注解就一定要配置数据库么?

思考4: 事务的传播级和隔离级别怎么配置?

二 .源码分析:

我们贴了一个@Transactional,事务就生效,那么该方法肯定被拦截了,所以会存在一个特殊的方法拦截器,MethodInterceptor,我们唯一的入口就是在主启动类贴了一个@EnableTransactionManagement注解,那么我们就从这里入手,看该注解做了

啥?

 

 通过上图,可知道,一个简单的注解就向spring容器中注入了那么多的类,我们关注的类是ProxyTransactionManagementConfiguration

我们看看该类的源码:

 

 通过上图,我们找到了一个事务拦截器,看看它的继承体系:

 

 它就是一个方法拦截器,方法执行时会拦截目标方法,那么我们的事务操作肯定都是在这个拦截器的拦截方法里,debug调试该方法即可

 

 

 继续根进:

@Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource(); //事务属性来源,啥意思呢?就是说哪些方法贴有事务注解,类似一个map将所有贴有事务注解的方法存了起来
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);//通过指定的目标方法,去找到该方法贴的事务注解的信息,注解里面有很多参数的
        final PlatformTransactionManager tm = determineTransactionManager(txAttr); //通过事务注解配置的属性来决定使用什么样的事务管理器
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);//给这个方法启个唯一标识

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { //默认tm都是不属于CallbackPrefering....,所以会进入下面的逻辑
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); //通过事务管理器,事务信息对象创建事务对象,在这一步将事务对象放到了ThreadLocal中

            Object retVal;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                retVal = invocation.proceedWithInvocation(); //开始执行目标方法
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);  //抛异常事务回滚
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo); //清空这次事务信息,也就是从ThreadLocal中移除
            }
            commitTransactionAfterReturning(txInfo); //如果没有抛异常就事务回滚
            return retVal;
        }

        else {
            final ThrowableHolder throwableHolder = new ThrowableHolder();  //另一个分支

            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                    TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                    try {
                        return invocation.proceedWithInvocation(); //执行目标方法
                    }
                    catch (Throwable ex) {
                        if (txAttr.rollbackOn(ex)) { //判断是否配置的异常,是就抛出去
                            // A RuntimeException: will lead to a rollback.
                            if (ex instanceof RuntimeException) {
                                throw (RuntimeException) ex;
                            }
                            else {
                                throw new ThrowableHolderException(ex);
                            }
                        }
                        else {
                            // A normal return value: will lead to a commit.
                            throwableHolder.throwable = ex;
                            return null;
                        }
                    }
                    finally {
                        cleanupTransactionInfo(txInfo);
                    }
                });

                // Check result state: It might indicate a Throwable to rethrow.
                if (throwableHolder.throwable != null) {
                    throw throwableHolder.throwable;
                }
                return result;
            }
            catch (ThrowableHolderException ex) {
                throw ex.getCause();
            }
            catch (TransactionSystemException ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                    ex2.initApplicationException(throwableHolder.throwable);
                }
                throw ex2;
            }
            catch (Throwable ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                }
                throw ex2;
            }
        }
    }

 上述方法非常清楚的展示了事务的执行原理:

 

 接着详细分析上面的每个流程:

  我先看看事务管理器接口的构成:该接口提供了事务提交方法,和事务的回滚方法

 

 因此,我们先分析最简单的事务回滚和事务提交方法:

1.completeTransactionAfterThrowing(txInfo, ex);事务回滚方法

 

 由源码知道,只有指定了哪些异常要回滚,事务才会回滚,否则事务就直接提交,但我们也没指定过,因此有必要看看txInfo.transactionAttribute.rollbackOn(ex)的逻辑

 

 

 

 默认没有配置指定异常,事务回滚也只会针对RuntimeException或者Error

 2. 接下来看看事务提交逻辑:commitTransactionAfterReturning(txInfo);

 由此看来,事务管理器是一个非常重要的接口,但他仅仅是个接口,具体事务怎么提交或者回滚,完全是由接口的实现类来决定,那么,我们就不一定需要是数据库,我们也可以针对redist,mogodb等来实现该接口,进

行对应的事务提交或者回滚,因此,开头提出的思考3:贴了@Transactional注解就一定要配置数据库么?答案就显而易见了

既然事务是由事务管理器来决定的,它是一个接口,那它是如何决定使用哪个事务管理器的呢?下面我们分析事务管理器的获取过程:

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

 

@Nullable
    protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
        // Do not attempt to lookup tx manager if no tx attributes are set
        if (txAttr == null || this.beanFactory == null) {
            return getTransactionManager(); //获取默认的事务管理器,由于没有配置,所以取不到
        }

        String qualifier = txAttr.getQualifier(); //如果我们在事务注解上指定了事务管理器的beanName,就从spring容器中或者
        if (StringUtils.hasText(qualifier)) {
            return determineQualifiedTransactionManager(this.beanFactory, qualifier);
        }
        else if (StringUtils.hasText(this.transactionManagerBeanName)) {  //如果前面不满足,本类配置事务管理器的BeanName就以其为准
            return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
        }
        else {
            PlatformTransactionManager defaultTransactionManager = getTransactionManager(); //上面的都不满足,就获取默认事务管理器
            if (defaultTransactionManager == null) {
                defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY); //没有的话,就从缓存中获取
                if (defaultTransactionManager == null) {
                    defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class); //缓存中没有的话,就直接从容器中获取PlatformTransactionManager
                    this.transactionManagerCache.putIfAbsent(
                            DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); //将获取到的事务管理器设置到缓存中
                }
            }
            return defaultTransactionManager;
        }
    }

上述的获取过程非常的简单:如果我们自己指定了事务管理器,就用我们自己的,没有指定就从spring容器获取默认的;这样我们是可以创建自己的事务管理器,然后配置给指定的事务:如

 

 

 最后看看我们配置的事务注解属性是如何被封装成对象的:先看看事务注解都有哪些属性:

public @interface Transactional {

    @AliasFor("transactionManager")
    String value() default ""; //指定事务管理器的BeanName

    @AliasFor("value")
    String transactionManager() default ""; //指定事务管理器的beanName

    Propagation propagation() default Propagation.REQUIRED; //事务的传播级别,面试时问spring事务的传播级别,答案就在这里了

    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 {};//异常时,哪些异常不需要回滚

}

上述的属性最终会被封装成:TransactionAttribute,回到最初拦截的源码:

 

 一直跟进去后,可以看到:

 

 

 

 可以看到,很多属性是跟数据库有关的,事务的隔离级别,传播级别等,他们实现的事务管理器是DataSourceTransactionManager,有兴趣的可以了解该事务管理器的实现,就可以知道事务的传播级别原理了

三.问题解答:

 

 

 

 

 

 

 

 问题1:直接调用doTest方法,doTest方法没有事务,然后doTest方法调用doTest2,doTest2有事务,那么doTest2的事务能生效么?debug在方法拦截器里

 

 

 执行结果发现,debug走不到事务拦截器那里,看看控制台的打印:

 

 由此得出结论,同一个类中,非事务方法调用事务方法,会导致事务失效,原因如下:

service.doTest();虽然service是代理类,但真正执行doTest时,是代理类中的被代理对象调用的

 

 既然我们知道了,doTest2并不是被代理类service直接调用的,那么如果我们就是使用service直接调用不就可以了么?我们第一能想到的是作为参数传入,改造如下

 

 启动再次测试:

 

 由此看到,事务生效了,那还有没更好的方案呢,因为这样传感觉不方便,代码回滚到之前,在service.doTest() debug调试

 

 引出来2个问题:1.条件如何设置,AopContext存代理对象到哪里

 

 

 

 由此可知,设置到了ThreadLocal中了,那条件又是啥:exposeProxy,该属性在@EnableAspectJAutoProxy(exposeProxy = true)中,于是我们改造代码:

 

 

 

 至此:当一个类中非事务方法,调用事务方法时,不生效的解决方案有上面2种;

最后一个问题:

 

 多个事务方法直接的链式调用,到底以哪个事务为准,这里生效的前提是数据库的事务管理器,或者对事务传播级别做处理的事务管理器:这里就可以顺便了解下事务传播级别了

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常

PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

我们以默认的事务传播级别来解答:

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。例如doTest2()——》doTest3(),对于doTest3()来说,如果它配置的事务隔离级别是REQUIRED,那么doTest3就会以doTest2配置的事务来执行

PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。例如doTest2()——》doTest3(),对于doTest3()来说,如果配置了该级别,doTest2配置了事务就使用,doTest2没有活动的事务就抛异常

其他的隔离级别以此类推。。。。。。。。。。。。。

 

 

 

 

 

 

 

 

 

 

 

 

 



           

 

posted @ 2021-04-16 12:01  yangxiaohui227  阅读(332)  评论(0编辑  收藏  举报