一、重现@Transaction失效的场景
有如下业务场景,新增订单后,自动发送短信,下面的代码在同一个类中:
@Transaction
public void addOrder(OrderInfo orderInfo){
orderMapper.insert(orderInfo);
try{
sendMesg(orderInfo );
}cach(Exception e){
e.printStrace();
}
}
@Transaction
public void sendMesg( OrderInfo orderInfo ){
mesgMapper.insert(orderInfo );
throws new RuntimeException("发送短信出现异常!");
}
上面的伪代码模拟新增订单后,自动发送短信的业务场景。两个操作被标识为事务,为了不影响发送短信出现异常影响订单的插入,在调用发送短信的方法时,通过 try....cach...捕获其异常并处理,不影响订单表的插入。
因为sendMesg标识为事务,其抛出异常后,事务按正常逻辑来说,事务会进行回滚,即短信表中不会插入记录。然而事与愿违,出现的结果是短信表也插入了记录。
二、重现异步注解失效的场景
1、异步注解@Async介绍
基于@Async标注的方法,称之为异步方法,这些方法在执行的时候,spring将会为其开辟独立的线程执行,调用者无需等待它的完成,即可继续其他的操作。
2、如何使用@Async
增加 aspectj 相关的依赖;
修改 spring配置文件,在配置文件中增加如下配置:
<task:annotation-driven executor = "annotationExecutor" />
<task:executor id="annotationExecutor" pool-size="20" />
在方法上加上@Async注解。
3、业务场景介绍
预定旅游套餐业务场景@Transaction
public void planTourismPackages(Order orderInfo){
dealPassengerTicketBusiness(orderInfo);
dealHotelBusiness(orderInfo);
sendMesg(orderInfo);
sendEmial(orderInfo);
}
@Async
public void sendMesg( OrderInfo orderInfo ){
mesgMapper.insert( orderInfo );
throws new RuntimeException("发送短信出现异常!");
}
预定旅游套餐时,首先处理机票预定业务,然后处理酒店预定业务,最后发送短信个邮件。发送短信和邮件属于辅助业务,可以让其异步执行,所以在方法上加了@Async注解,让其异步执行。为了演示效果,我将planTourismPackages方法标识为事务处理,故意让sendMesg方法抛出异常。按我们预想,因为sendMesg方法异步执行,其抛出异常应该不会影响planTourismPackages事务的提交,机票预定表和酒店预定表应该会插入记录。但是,事与愿违,sendMesg抛出异常导致planTourismPackages事务会滚了,说明@Async并未生效。
三、原因分析
为什么会出现上面的事务和异步注解失效呢?
spring声明式事务和异步注解的实现都是基于spring aop,即对标识的方法进行增强。spring aop的底层实现原理是 jdk 动态代理。因为事务注解方法,异步方法的调用的方法在同一个类中,所以发送短信方法是在调用方法的代理对象中执行的,没有对发送短信方法进行增强。如下图:
orderMapper.insert( orderInfo );
try{
this.sendMesg( orderInfo );
}cach(Exception e){
e.printStrace();
}
public void planTourismPackages(Order orderInfo){
dealHotelBusiness( orderInfo );
dealPassengerTicketBusiness( orderInfo );
this.sendMesg( orderInfo );
this.sendEmial( orderInfo );
}
四、解决方案
要解决上面的问题,必须要实现发送短信方法的增强,使其能够成为事务或者异步方法,即让代理生效。spring的解决方案如下:
1、在spring配置文件xml新增如下语句:
先开启cglib代理,开启 exposeProxy=true,暴露代理对象
<aop:aspectj-autoproxy expose-proxy="true"/>
2、使用AopContext 获取当前对象的动态代理。
修改配置文件后,代码修改,用获取到的动态代理去执行发送短信方法,如下:
TourServer currentProxy = (TourServer )AopContext.currentProxy();
currentProxy.sendMesg(orderInfo);
currentProxy.sendEmail(orderInfo);
网友评论