springboot事务不生效的原因分析

一、背景

  • springboot项目中,使用@Transactional注解,非直接调用的方法注解会失效,如:UserController(A方法)->UserServiceImpl(B方法)->UserServiceImpl(C方法),把@Transactional加在B方法有效,加在C方法无效

二、目的

    1. 揭秘为什么事务注解加在C方法无效
    1. 如何使C方法的事务有效

三、内容

1、自己写代码控制事务提交
  • 代码示例
        public Boolean transactional(String driverClassName, String url, String username, String password) {
        Connection connection = null;
        Statement statement = null;
        try {
            connection = getConnection(driverClassName, url, username, password);
            //设置自动提交为false
            connection.setAutoCommit(false);
            // 获取执行sql的对象
            statement = connection.createStatement();
            String sql = "xxxxxx";
            statement.execute(sql);
            //提交事务
            connection.commit();
        } catch (Exception exception) {
            try {
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException sqlException) {
                //省略...
            }
            throw new UpgradeException(ResultTypeEnum.System_Exception, exception.getMessage());
        } finally {
           //省略...
        }
        return true;
    }
  • 分析:通过connection.setAutoCommit(false)设置事务不自动提交,通过connection.commit()控制事务的提交点,通过connection.rollback()来控制事务回滚;思考:框架是如何实现的呢?是不是也有这几个步骤,只是它的功能更加强大?
2、直接调用的方法上加事务
  • 代码示例
    @Test
    void testTransactional_1() {
      userService.testTransactional_1();
    }
    @Transactional
    @Override
    public void testTransactional_1() {
        User user = new User();
        user.setUsername("zzg1");
        user.setAge(20);
        user.setPassword("123456");
        userMapper.insert(user);
        int i = 1 / 0;
    }
  • 执行前
    在这里插入图片描述

  • 执行后
    在这里插入图片描述
    在这里插入图片描述

  • 总结:这是常见的事务注解形式,直接加在B方法上,事务是生效的

3、非直接调用的方法上加事务
  • 代码示例
   @Test
   void testTransactional_2() {
       userService.testTransactional_2();
   }
    @Override
    public void testTransactional_2() {
        this.testTransactional_0();
    }

    @Transactional
    @DS("master")
    public void testTransactional_0() {
        User user = new User();
        user.setUsername("zzg1");
        user.setAge(20);
        user.setPassword("123456");
        userMapper.insert(user);
        int i = 1 / 0;
    }
  • 执行前
    在这里插入图片描述

  • 执行后
    在这里插入图片描述
    在这里插入图片描述

  • 总结:加在非直接调用的C方法上,事务失效

4、Debug调试,分析执行过程
  • 关键点1:org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept

  • 关键点2:org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation

  • 关键点3:ReflectiveMethodInvocation.java:186

  • 关键点4:org.springframework.jdbc.datasource.ConnectionHolder#getConnection

  • 关键点5:DataSourceTransactionManager.java:287

  • 关键点6:org.springframework.jdbc.datasource.ConnectionHolder#getConnection

  • 关键点7:com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor#prepareStatement

  • 关键点8:MybatisSimpleExecutor.java:54

  • 关键点9:org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing

  • 关键点10:org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo#getTransactionManager

  • 关键点11:org.mybatis.spring.transaction.SpringManagedTransaction#connection

  • 关键点12:java.sql.Connection#rollback()

  • 分析总结:这是执行事务生效的testTransactional_1()方法的关键步骤关键点1、2、3是GGLIB动态代理的处理,关键点4获取数据库连接关键点5设置事务提交为false关键点8执行sql关键点9事务异常捕获,关键点12事务回滚;通过这些分析可以得出,事务生效是被CGLIB代理之后,进行的一些增强方法的操作,核心本质的代码与我们自己写的事务代码没有区别

    思考:如何找到这些关键步骤呢?

5、动态代理类的字节码反编译分析
  • HSDB 连接进程

    • jps获取进程ID
      在这里插入图片描述
      在这里插入图片描述

    • 执行命令java -classpath sa-jdi.jar "sun.jvm.hotspot.HSDB"
      在这里插入图片描述
      在这里插入图片描述

    • File->Attach to HotSpot process ->Enter Project ID
      在这里插入图片描述
      在这里插入图片描述

  • Tools->Class Brower
    在这里插入图片描述

  • HSDB 输入目标类名称,找到动态代理之后的类
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 生成的类保存在在jdk1.8.0_131\lib的目录
    在这里插入图片描述

  • 复制到idea
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvApCe8v-1691754881563)(images/81e264cf8cb6bd3a106a99d9aa4b1527019f70f303973e86064b2fc5dc93fbfa.png)]

  • 关键代码截图:

    在这里插入图片描述
    在这里插入图片描述

  • 分析总结:可以分析得出CGLIB是通过继承的方式来代理的;不论是testTransactional_2()还是testTransactional_0()都进行了方法拦截操作。var10000.intercept(this, CGLIB$testTransactional_0$1$Method, CGLIB$emptyArgs, CGLIB$testTransactional_0$1$Proxy);这行代码会进入步骤4中进行注解分析;如果没有注解,则直接调用父类的方法;到这里其实就可以得出为什么注解会失效了;形如:A方法->UserServiceImpl(B方法、C方法,B方法调用C方法)UserServiceImpl代理之后,UserServiceImpl’B’方法C’方法B’方法本质调用的是B方法只是对B方法进行了增强,而B方法调用的还是C方法并不是C’方法,这就是没有生效的原因)

6、如何解决事务注解非直接注解带来的注解无效问题
  • 方式一:在B方法中,明确调用的是C’的方法 AopContext.currentProxy();
  • 代码示例:
    @Override
    public void testTransactional_4() {
        //明确使用代理类的对象
        UserServiceImpl userService = (UserServiceImpl) AopContext.currentProxy();
        userService.testTransactional_0();
    }
  • 方式二:在application启动类加上注解@EnableAspectJAutoProxy(exposeProxy = true),在目标类中注入private TransactionTemplate transactionTemplate;
  • 代码示例:
    private void testTransactional_01() {
        transactionTemplate.execute(transactionStatus -> {
            User user = new User();
            user.setUsername("zzg1");
            user.setAge(20);
            user.setPassword("123456");
            userMapper.insert(user);
            int i = 1 / 0;
            return Boolean.TRUE;
        });
    }
7、拓展:如何找到步骤4中的这些关键点呢?

在这里插入图片描述

  • 如上图所示,一个是事务生效堆栈信息,一个是事务不生效堆栈信息,对比分析可以知道,事务生效的堆栈信息里,有关于事务的处理,针对这些堆栈信息去Debug,梳理逻辑,然后进一步的查看具体的方法,查找我们想要的信息

附录:
相关文章推荐:Spring 官方推荐的 @Transactional 还能导致生产事故?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值