被标记为事务的方法互相调用的坑(上)

栏目: Java · 发布时间: 7年前

内容简介:相信大家一定用过Spring中的注解型事务,配合上Spring Boot,只需要在方法上打一个@Transactional 就可以完成,真香。但是如果大家对其中的机制一知半解的话,可能一不小心就会掉进坑,然后久久无法爬出来。下面我就分享下

相信大家一定用过Spring中的注解型事务,配合上Spring Boot,只需要在方法上打一个@Transactional 就可以完成,真香。

但是如果大家对其中的机制一知半解的话,可能一不小心就会掉进坑,然后久久无法爬出来。

下面我就分享下 被标记为事务的方法互相调用的坑

首先我写两个事务方法:

@Autowired
    AccountMapper mapper;

    @Transactional
    @Override
    public void insertCodeBear() {
        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }

    @Transactional
    @Override
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
    }
复制代码

现在我想在insertCodeBear方法里面调用insertCodeMonkey方法,但是insertCodeMonkey不是很重要,就算失败,也不能影响到insertCodeBear方法的执行,但是insertCodeMonkey该回滚的还是要回滚,我们很容易写出如下代码:

@Autowired
    AccountMapper mapper;

    @Transactional
    @Override
    public void insertCodeBear() {
        try {
           insertCodeMonkey();
        } catch (Exception ex) {
        }
        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
        int a = 1 / 0;//自杀代码,便于测试
    }
复制代码

在第二个方法中,用了自杀代码,便于测试。

看上去一点问题都没有:第一个方法会成功,第二个方法会失败并且回滚。但是仅仅是看上去,当我们运行一下,会发现奇怪的事情发生了:

被标记为事务的方法互相调用的坑(上)

两个方法竟然都成功了!!Why?

为了排查问题,需要开启一下 有关事务 的日志,在 配置文件 中加上下面的配置:

logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=debug
复制代码

然后运行,看下控制台打印的内容:

被标记为事务的方法互相调用的坑(上)

图片可能有点模糊,大家可以在新标签页中打开这图片,可以看到这里分明只开了一个事务,而且事务的传播行为是PROPAGATION_REQUIRED,这是事务的默认传播行为,也就是这里只开启了insertCodeBear方法的事务,并没有开启insertCodeMonkey的事务。

这是什么原因?为了更好的说明问题产生的原因,我需要手写一个AOP。

在此之前大家要达成一个共识,@Transactional 其实也是通过AOP去实现的。

被标记为事务的方法互相调用的坑(上)

AOP有几种实现方式,我这里采用JDK动态代理的方式:

代码入口:

public class Main {
    public static void main(String[] args) {
        BookServiceImpl impl = new BookServiceImpl();
        InvocationHandler myInvocationHandler = new MyInvocationHandler(impl);
        Object o = Proxy.newProxyInstance(myInvocationHandler.getClass().getClassLoader(),
                impl.getClass().getInterfaces(), myInvocationHandler);
        ((IBookService) o).add();
    }
}

复制代码

接口:

public interface IBookService {
    void add();

    void delete();
}
复制代码

实现类:

public class BookServiceImpl implements IBookService {
    public void add() {
        delete();
        System.out.println("add");
    }

    public void delete() {
        System.out.println("delete");
    }
}
复制代码

切面定义:

public class MyInvocationHandler implements InvocationHandler {
    private Object obj;

    public MyInvocationHandler(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始啦,小伙子");
        method.invoke(obj, args);
        System.out.println("结束啦,小伙子");
        return null;
    }
}
复制代码

在Main入口里面调用了实现类的代理对象,调用了add方法,add方法里面又调用了delete的方法。很简单吧。按照我们的想法,应该是打印出两次 切面中定义的话,但是事实是 只打印了一次:

被标记为事务的方法互相调用的坑(上)

让我们在切面方法中加上这行代码:

System.out.println("方法是" + method.getName());
复制代码

看看是哪个方法进入到了这里。

运行:

被标记为事务的方法互相调用的坑(上)

add方法进入到了这里,但是delete方法却没有进来。

让我们再回到第一个例子,为了让大家看的清楚一点,我再贴上insertCodeBear被调用的代码:

@RestController
@RequestMapping("/CodeBear")
public class HelloWorldController {
    @Autowired
    AccountService service;

    @GetMapping("/insert")
    public void insert() {
        service.insertCodeBear();
    }
}
复制代码

AccountService 是一个接口,里面定义了insertCodeBear和insertCodeMonkey虚方法。 我们打一个断点在

service.insertCodeBear();
复制代码

这里,然后调试看下service是一个什么东西:

被标记为事务的方法互相调用的坑(上)

你会发现,service已经不是简单的AccountService 的实现类了,而是实现类的代理对象,从这里也可以看出,其实@Transactional也是通过AOP去实现的。

通过两个例子,可以得到一个结论: 只有调用代理对象的方法才能被拦截,所以 在方法A中直接调用方法B,方法B是不会被拦截的

这也就是为什么insertCodeMonkey的事务没有被开启的原因了,因为insertCodeMonkey方法是insertCodeBear直接调用的。

那么,这个问题该如何解决呢?在下一篇博客,我会采用几种方式来解决这个问题(这篇博客已经比较长了,因为加上了很多看上去没什么用的“废话”,因为可以直接写出结论,然后再写解决方案就是了。但是我还是很详细的,把“废话”都写出来了,就是因为分析问题的思路才是最重要的 )。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

High Performance Python

High Performance Python

Andrew Lewis / O'Reilly Media, Inc. / 2010-09-15 / USD 34.99

Chapter 1. Introduction Section 1.1. The High Performance Buzz-word Chapter 2. The Theory of Computation Section 2.1. Introduction Section 2.2. Problems Section 2.3. Models of Computati......一起来看看 《High Performance Python》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试