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

相信你们必定用过Spring中的注解型事务,配合上Spring Boot,只须要在方法上打一个@Transactional 就能够完成,真香。spring

可是若是你们对其中的机制只知其一;不知其二的话,可能一不当心就会掉进坑,而后久久没法爬出来。bash

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

首先我写两个事务方法:ide

@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;//自杀代码,便于测试
    }
复制代码

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

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

image.png
两个方法居然都成功了!!Why?

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

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

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

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

这是什么缘由?为了更好的说明问题产生的缘由,我须要手写一个AOP。3d

在此以前你们要达成一个共识,@Transactional 其实也是经过AOP去实现的。

image.png

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的方法。很简单吧。按照咱们的想法,应该是打印出两次 切面中定义的话,可是事实是 只打印了一次:

image.png
让咱们在切面方法中加上这行代码:

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

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

运行:

image.png

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是一个什么东西:

image.png

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

经过两个例子,能够获得一个结论:只有调用代理对象的方法才能被拦截,因此 在方法A中直接调用方法B,方法B是不会被拦截的

这也就是为何insertCodeMonkey的事务没有被开启的缘由了,由于insertCodeMonkey方法是insertCodeBear直接调用的。

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

相关文章
相关标签/搜索