【BUG】@Transactional注解在同类方法调用中不生效
后台代码
今天做系统开发的时候,遇到了一个BUG:@Transactional注解在同类方法调用中不生效
代码如下:
/**
* @description: 快递100接口服务类
* @date 2021/11/23
*/
@Service
public class SysExpressServiceImpl implements ISysExpressService {
/**
* 处理快递100推送请求并响应
* @param request 快递100的推送请求
* @return 响应
*/
@Override
public SubscribeResp handleCallBack(HttpServletRequest request) throws Exception {
// 获取参数
String param = request.getParameter("param");
String sign = request.getParameter("sign");
logger.info("快递100订阅推送回调结果:{}|{}",param,sign);
// 解析报文
SubscribePushParamResp backResp = new Gson().fromJson(param, SubscribePushParamResp.class);
handleResponse(param,backResp);
return getSubscribeResp();
}
}
/**
* 业务处理
*/
@Transactional
public void handleResponse(String param,SubscribePushParamResp backResp) throws Exception {
/**
* 处理之前,将报文存入数据库,方便出问题后排查
* 每次推送的报文,都要入库
*/
saveExpressInfo();
String expressNum = backResp.getLastResult().getNu();
SysExpressData expressData = new SysExpressData();
expressData.setExpressNum(expressNum);
expressData.setReceiveText(param);
expressData.setStatus("0");
expressDataMapper.insertSysExpressData(expressData);
// 业务处理
SysExpress express = expressMapper.selectSysExpressByNum(expressNum);
//当message为“3天查询无记录”或“60天无变化时”status= abort,处理逻辑是重新订阅推送服务
if("abort".equals(backResp.getStatus().trim())){
express.setRemark("status= abort,重新订阅推送服务");
List<SysExpress> expressList = new ArrayList<>();
expressList.add(express);
subscribe(expressList);
}
// shutdown说明快递被签收,推送服务中止
if("shutdown".equals(backResp.getStatus().trim())){
express.setStatus(3L);
expressMapper.updateSysExpress(express);
}
// 解析报文前,先删除sys_express_info表中的数据,只保留最新的数据。
expressInfoMapper.deleteSysExpressInfoByNum(expressNum);
// 获取data数据集,解析后存入数据库
List<SubscribePushData> dataList = backResp.getLastResult().getData();
for (SubscribePushData data : dataList) {
SysExpressInfo info = new SysExpressInfo();
info.setCompanyCode(backResp.getLastResult().getCom());
info.setContext(data.getContext());
info.setExpressNum(expressNum);
info.setStatus(data.getStatus());
info.setTime(DateUtils.parseDate(data.getFtime()));
expressInfoMapper.insertSysExpressInfo(info);
}
}
可以看到,handleCallBack方法和handleResponse位于同一个类中,且handleCallBack内部调用了handleResponse方法。
handleResponse方法上有@Transactional注解,而handleCallBack没有
BUG产生
数据库的操作出现异常了,却没有正常的进行回滚。
事务失效的原因分析
不同
类方法调用
类A的方法a调用类B的方法b,只要方法a 或 b配置了事务,此时事务会生效。
若两个方法都配置了事务,两个事务具体以何种方式传播,取决于设置的事务传播特性
spring事务的默认方式是 REQUIRED
(有事务则加入,没有则创建)
在这种情况下,如果方法a和b都加了@Transactional
注解,因为是a调用b,那么b方法会加入到a方法的事务中执行。
同
类方法调用
天坑来了!
-
举例A:
同类内的方法a调用方法b(方法a不加@Transactional
注解)
无论被调用的方法b是否加了@Transactional
注解,事务都将失效。 -
举例B:
同类内的方法a调用方法b(方法a加@Transactional
注解)
此时方法b的事务虽然不生效,但方法a的事务生效,对于方法b中抛出的异常也会回滚。
举例B的原因分析
spring 在扫描bean的时候,会扫描方法上是否包含**@Transactional注解**,如果包含,spring会为这个bean动态生成子类
(即包装好的proxy代理类),代理类是继承原来那个bean的。
(在我的方法中,假定是 SysExpressServiceImplproxy extends SysExpressServiceImpl
)
此时,当这个有注解的方法在外部,注意,是在外部被调用时,实际上是交由代理类来调用的,代理类在调用前就会启动transaction。
然而,如果这个有注解的方法是被同类中的其他方法调用,那么该方法的调用并没有通过代理类,而是直接通过原来的bean(相当于this.进行调用
),绕过代理对象。
此处的 this (= new SysExpressServiceImpl
),因此直接调用方法,所以就不会启动transaction,因此事务失效。
AOP原理跟事务一样,往大里说是动态代理,往小里说是反射机制。
解决方案
我的代码是同类调用,事务失效,应该如何解决?
解决方案:handleCallBack 和 handleResponse都加上 @Transactional注解