@Transactional注解失效原因之一(同类不同方法)

    @TransaTranctional之前一直以为只要在想要加注解的方法上加@Transactional注解就可以开启事务了,而对于其原理是不清楚的,但是其实其中的坑有点多,除了网上各大博主总结的几种情况(详情请看@Transactional失效的6种情况),自己在测试的也遇到其中一种情况(这是因为这是基于Spring的AOP代理实现的,后面搞懂了。。。),下面分析一下这种情况。


一个很简单的demo,接口代码省略。。

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    public TestMapper testMapper;

    @Autowired
    public TestBService testBService;

    @Transactional
    @Override
    public void a() {
        try {
            Test test = new Test();
            test.setId(1);
            test.setName("a");
            testMapper.insert(test);
            //直接调用b方法
            this.b();
        } catch (Exception e) {
           e.printStackTrace();
        }
    }

    @Transactional
    @Override
    public void a1() {
        try {
            Test test = new Test();
            test.setId(2);
            test.setName("a1");
            testMapper.insert(test);
            //新建一个类调用b方法 此b方法与下面的b方法一模一样
            testBService.b();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b() {
        Test test = new Test();
        test.setId(3);
        test.setName("b");
        testMapper.insert(test);
        //模拟抛出一个RunTimeException
        int i = 1 / 0;
    }

}
@RestController
public class TestController {

    @Autowired
    TestService testService;

    @RequestMapping("/a")
    public String a(){
        testService.a();
        return "a";
    }

    @RequestMapping("/a1")
    public String a1(){
        testService.a1();
        return "a1";
    }
}

当访问 /a接口时,因为a方法默认事务传播行为为Propagation.REQUIREDb方法事务为 REQUIRES_NEW

按照之前错误的理解方式的预期结果,b方法会开启一个新的事务,因为抛出异常,所以回滚了,id为3的用户插入不进去,但是a方法是单独的另外一个事务,并且通过trycatch捕获了b的异常(被trycatch捕获了异常,相当于没有发生异常,所以事务正常提交,开发中要注意),是可以插入进去的,但是经过事实证明,都插入进去了,为什么b方法也会插入进去呢?经过一步步debug,以及查阅相关资料。@Transactional是通过SpirngAop实现的

spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

也就是说 我们this.b()方法实际没有使用代理类对象,我们通过debug也可以看到这一点,this这里不是指的代理类,所以事务没有生效。

如何解决呢?可以像a1()方法新建一个类,在调用b方法的时候会重新生成一个代理类,此时b方法@Transactional才会生效。b方法回滚,所以只有id为2的用户插入进去

@ Transactional在哪里开启事务的呢?经过一步步debug 发现是在

invokeWithinTransaction()->createTransactionIfNecessary()->getTransaction()->startTransaction()->doBegin() 在doBegin方法中可以看到熟悉的jdbc操作事务的步骤。。。。
protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        Connection con = null;

        try {
            if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = this.obtainDataSource().getConnection();
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }

                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            txObject.setReadOnly(definition.isReadOnly());
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }

                con.setAutoCommit(false);
            }

            this.prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);
            int timeout = this.determineTimeout(definition);
            if (timeout != -1) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }

            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());
            }

        } catch (Throwable var7) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.obtainDataSource());
                txObject.setConnectionHolder((ConnectionHolder)null, false);
            }

            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值