记录一次由事务可重复读引起的问题

原由是现金贷在下单时为了让订单创建的事务时间尽可能小在插入订单后单独起一个线程去机审,结果在机审中根据这个新订单号却查不到这个订单报订单不存在,原先以为是因为创建订单的事务还没提交就去查所以查不到,所以在机审的代码里写了个循环去查,中途sleep 2s。代码如下:

      /**
     * 添加订单
     *
     * @param requestStr
     * @return
     * @see com.ps.gnxjd.core.api.service.order.IOrderService#userOrderAdd(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    @RequestMapping("userOrderAdd")
    @Transactional
    @Override
    public ResponseMessage<?> userOrderAdd(String requestStr) {
...................
..................
orderMapper.insertSelective(order);
// 插入订单扩展认证信息 orderMapper.insertOrderExtendAuth(request, order.getId(), user.getId(), request.getHeader().getIp(), request.getHeader().getDeviceType()); // 更新用户单数 userMapper.updateOrderCount(user.getId(), order.getId());

    orderMapper.insertOrderExtendAuth(request, order.getId(), user.getId(), request.getHeader().getIp(),
                    request.getHeader().getDeviceType());
// 判断当前用户认证状态 如果已认证 则直接机审 否则跳过进入待机审 if (info.getCrawlStatus() == CrawlStatus.AUTHED.getCode()) { OrderExtend orderExtend = new OrderExtend(); orderExtend.setUserId(user.getId()); orderExtend.setPhone(info.getPhone()); orderExtend.setIdCard(info.getIdCard()); orderExtend.setOrderId(order.getId()); orderExtend.setOrderNo(orderNo); orderExtend.setOrderDevicetoken(request.getDevicetoken()); orderExtend.setReportNo(info.getReportNo()); orderExtend.setIpAddress(request.getHeader().getIp()); orderExtend.setXinyanToken(request.getXinyanToken()); orderExtend.setBlackBox(request.getBlackBox()); orderExtend.setOrderAddress(request.getOrderAddress()); new MachineApproveThread(orderExtend).start(); } response.setData(orderNo); logger.info(String.format("应答:%s", JsonUtil.toJSONString(response))); return response;
    }
    }

    public class MachineApproveThread extends Thread {

        private OrderExtend orderExtendInfo;

        public MachineApproveThread(OrderExtend orderExtendInfo) {
            this.orderExtendInfo = orderExtendInfo;
        }

        @Override
        public void run() {
            dealWith(orderExtendInfo);
        }

        private void dealWith(OrderExtend orderExtendInfo) {

            Date now = DateUtil.getCountyTime();
            // 调用机审
             getTongdunData()
     ...................
...................

@Transactional
    @Override
    public Map<String, Object> getTongdunData(Integer orderId, Map<String, String> strMap, String filePath,String appType) throws HjException {

        OrderExtend orderExtend = null;
        try {
            int index = 0;
            while (orderExtend == null && index++ < 20) {
                orderExtend = orderMapper.selectOrderExtraByOrderId(orderId);
                Thread.sleep(2000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.info(e.getMessage(), e);
        }
    }

 上面为了解决userOrderAdd事务还没结束,orderMapper.selectOrderExtraByOrderId取不到值的问题,在取的时候加了个循环,每次sleep了2s。但运行结果看日志,orderMapper.selectOrderExtraByOrderId这句只运行了一次。然后看插入order表和更新order表的时间,中间差了40s,这说明在循环中连续跑了20次。后来想到了mysql数据库默认离级别是可重复读,在一个事务中每次读取到的结果都是一样的,不管在另外一个事务中插入的操作有没有提交事务。想到这里,那日志为什么只打了一次?也许这是mybatis优化的机制吧。其实一开始想解决的问题没错,都是因为在新线程开始时,主线程还没结束,而在此时的快照中新线程无法读到主线程插入的orderExtend,而尽管做了sleep,但此时也无法读到另外事务中提交的记录了。既然是由可重复读引起了,那在事务中sleep就没用了,必须在进入事务之前sleep。改了下代码:只要在事务之前sleep,确保进入新事务后orderExtend已经插好就可以了。

public class MachineApproveThread extends Thread {

        private OrderExtend orderExtendInfo;

        public MachineApproveThread(OrderExtend orderExtendInfo) {
            this.orderExtendInfo = orderExtendInfo;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
            dealWith(orderExtendInfo);
        }

        @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
        private void dealWith(OrderExtend orderExtendInfo) {
           .......................
           ......................

     }

 但要注意一种情况,就是在同一个类中调的方法有时会出现 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)标签失效的情况。

如:

@Service
public class QianchengApiOrderService {    

   @SuppressWarnings("unchecked")
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
      public void dealWithAdditional(String ua, String call, String args, String sign, String timestamp, String ip,
            ResponseMessageQCApi response) {
      。。。。。

            orderExtendIndb = readNewOrderExtend(partnerOrderNo);


      。。。。。。。

  }

}




@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public OrderExtend readNewOrderExtend(String partnerOrderNo) {
        // 如果订单不存在
        OrderExtend orderExtendIndb = orderMapper.selectOrderByThirdOrderNo(partnerOrderNo);
        return orderExtendIndb;
    }

这样不能解决可重复读的问题,因为这样调自己类内部的方法没有激活spring的aop机制,其实这个Propagation.REQUIRES_NEW是失效的,并没有新起一个事务,要这样写才行:

@Service
public class QianchengApiOrderService {    

   @SuppressWarnings("unchecked")
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
      public void dealWithAdditional(String ua, String call, String args, String sign, String timestamp, String ip,
            ResponseMessageQCApi response) {
      。。。。。

            orderExtendIndb = qianchengApiOrderService.readNewOrderExtend(partnerOrderNo);


      。。。。。。。

  }

}




@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public OrderExtend readNewOrderExtend(String partnerOrderNo) {
        // 如果订单不存在
        OrderExtend orderExtendIndb = orderMapper.selectOrderByThirdOrderNo(partnerOrderNo);
        return orderExtendIndb;
    }
喜欢艺术的码农
原文地址:https://www.cnblogs.com/zjhgx/p/9976914.html