【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注解

推荐博客

spring声明式事务 同一类内方法调用事务失效

@Transactional 详解

posted @ 2021-11-26 16:28  layman~  阅读(376)  评论(0编辑  收藏  举报