Spring AOP Transactional注解不能用于内部调用问题

Spring框架中的Transactional注解是几个比较常用注解之一,作用就是用于事务的管理。如果系统接的Mybatis的数据库,那么Spring的Transactional就会接替sqlSession的生命周期管理。
它的用法非常简单,就是把它标注在需要事务的方法上面即可。例如

1
2
3
4
@Transactional(rollbackFor = Exception.class)
public void createBidOffer() {
insertBidAndOffer();
}

但是并不是放到任何方法上,它都会生效的。

类中方法调用

一个例子,有如下调用逻辑:
controller部分代码

1
2
3
4
5
@RequestMapping("/transaction")
public Response testTransactionAop() {
tradeService.createBidOffer();
return new Response();
}

TradeService部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void createBidOffer() {
....
insertBidAndOffer();
....
}

@Transactional(rollbackFor = Exception.class)
public void insertBidAndOffer() {
insertBid();
}

private Bid insertBid() {
Bid bid = new Bid();
bid.setTotalAmount(100);
bid.setRestAmount(100);
bidMapper.insert(bid);
return bid;
}

TradeService的对外接口是createBidOffer(),但不想整个方法做事务,只对于insertBidAndOffer()里面的操作做事务。
可惜的是以上这种写法是无效的。虽然执行时候不会有任何报错异常,但一旦insertBidAndOffer()方法执行过程中抛出异常,事务是不会生效的,即使方法是public也没用(Transaction注解要求作用于public的方法上)。

@Transactional 的AOP切点

Spring AOP其实是与IOC配合使用的,而Spring AOP是用动态代理的技术。也就是说一个类被IOC所注入生成的对象被Spring动态代理成一个新的代理对象。
无效的原因其实就很容易理解了,我们在外部调用这个动态代理对象,会在代理的时候增强对象,但在对象的内部调用的时候,调用的还是原来的对象的方法,该方法明细不会被AOP增强。上面的例子,在Spring框架里,TradeService注入到controller中,生成一个代理的tradeService对象,在controller调用tradeService方法的时候,被代理的方法拦截。该方法中会找出这个AOP连接点的Advice,然后切入执行(也就是执行@Transactional)。若是调用对象内容方法的时候,就不会被代理发放拦截的了。
下图即为拦截切入点,框内为切入后执行该切点的Advice,事务就在其中执行

P.S. 如果该类实现了接口,Java的Proxy做动态代理;如果没有实现,则是CGLIB做的动态代理(以子类的方式)。

解决方法

一个原则:事务注解只用于对外的方法上。
如果想实现对象内部调用的逻辑,最简单的方法就是,把逻辑抽出来,放到另外一个类中取调用即可。

源码参考

Java web 项目脚手架: https://github.com/Zack-Ku/java-web-scaffold

听说你想请我喝下午茶?~