記錄一次由事務可重復讀引起的問題


原由是現金貸在下單時為了讓訂單創建的事務時間盡可能小在插入訂單后單獨起一個線程去機審,結果在機審中根據這個新訂單號卻查不到這個訂單報訂單不存在,原先以為是因為創建訂單的事務還沒提交就去查所以查不到,所以在機審的代碼里寫了個循環去查,中途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;
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM