zoukankan      html  css  js  c++  java
  • 彻底弄懂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没有活动的事务就抛异常

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

    
    
    
    

               

  • 相关阅读:
    STM32F407 开发环境搭建 程序下载 个人笔记
    用bootstrap_table实现html 表格翻页
    STM32F407 正点原子 资料网址记录
    C51 动态数码管 个人笔记
    C51 继电器 个人笔记
    谷歌浏览器截长图
    C51 原创电子琴 (蜂鸣器/计时器/中断/矩阵按键)
    从零自学Hadoop(01):认识Hadoop
    Centos修改DNS重启或者重启network服务后丢失问题处理
    初次体验VS2015正式版,安装详细过程。
  • 原文地址:https://www.cnblogs.com/yangxiaohui227/p/14666362.html
Copyright © 2011-2022 走看看