基於狀態機的樂觀鎖的實現

  發貨系統和訂單系統基於Spring-Boot項目,其中springboot整合了mybatis,log4j2等 ,項目中使用到了generator代碼生成工具,生成dao/domain/mapper.xml文件

  • 發貨系統模擬(target-service)
    • Controller層實現

複製代碼

/**
 * @description: 模擬倉庫發貨類
 * @author: GaraYing
 * @create: 2018-08-14 09:53
 **/

@RestController
@RequestMapping("/bank")
public class BankController {
    private Logger logger = LoggerFactory.getLogger(getClass());
    
    /** 
    * @Description: 遠程提供發貨處理接口
    * @Param: [orderid]
    * @return: java.lang.String
    * @Author: GaraYing
    * @Date: 2018/8/15 14:05
    */
    @RequestMapping(value = "/handleOrder")
    public String handleOrder(@RequestParam(required = false) String orderid){

        logger.info("收到訂單號:" + orderid + ",正在出貨處理中……");
        try {
            Thread.currentThread().sleep(10000);
        }catch (Exception e){
            logger.error("出現錯誤了"+e.getMessage());
            e.printStackTrace();
            return "-1";
        }
        return "0";
    }
}

複製代碼

  • 訂單系統模擬(client-service)
    • mapper.xml文件

複製代碼

<select id="findOrderById" resultMap="result">
        SELECT * FROM t_order where orderId = #{orderid};
    </select>

    <update id="update" parameterType="com.gara.lock_demo.domain.Order"
            flushCache="true">
        <![CDATA[

        update t_order
        set orderStatus =
        #{orderStatus,jdbcType=VARCHAR}
        where
        orderId = #{orderId,jdbcType=VARCHAR}

        ]]>
    </update>

    <insert id="updateByVersion" parameterType="com.gara.lock_demo.domain.Order"
            flushCache="true">
     <![CDATA[

        update t_order
        set orderStatus =
        #{orderStatus,jdbcType=VARCHAR},
        version = version+1
        where
        orderId = #{orderId,jdbcType=VARCHAR} and version = #{version}

        ]]>
    </insert>

複製代碼

    • Controller實現

複製代碼

/**
 * @description: 消費端
 * @author: GaraYing
 * @create: 2018-08-14 14:33
 **/
/*
    接口層:對外開放接口路徑及地址 http://127.0.0.1:8080/order/sendOrder?orderid=1
 */
@RestController
@RequestMapping("/order")
public class CustController {

    @Autowired
    private OrderService orderService;

    @RequestMapping("/query")
    @ResponseBody
    public Object query(@RequestParam(required = true) String orderid) {
        Order order = orderService.findOrderById(orderid);
        return order;
    }


    @RequestMapping("/sendOrder")
    @ResponseBody
    public String sendOrder(@RequestParam(required = true) String orderid) {
        Order order = orderService.findOrderById(orderid);
        return orderService.sendOrder(order);
    }

    @RequestMapping("/sendOrderByTemplate")
    @ResponseBody
    public String sendOrderByTemplate(@RequestParam(required = true) String orderid) {
        Order order = orderService.findOrderById(orderid);
        return orderService.sendOrderByTemplate(order);
    }

    @RequestMapping("/sendOrderByTemplateThread")
    @ResponseBody
    public String sendOrderByTemplateThread(@RequestParam(required = true) String orderid) {
        Order order = orderService.findOrderById(orderid);
        for (int i = 0; i < 6; i++) {
            Thread t = new Thread(new ExcuteThread(order));
            t.start();
        }
        return null;
    }

    private class ExcuteThread implements Runnable {

        private Order order;

        public ExcuteThread(Order order) {
            this.order = order;
        }

        @Override
        public void run() {
            orderService.sendOrderByTemplateThread(order);
        }
    }
}

複製代碼

  • TemplateConfig核心配置類,用於調用第三方(倉庫系統)接口,使用RestTemplate.getForEntity()方法,調用遠程發貨接口

複製代碼

/**
 * @description: Config 類
 * @author: GaraYing
 * @create: 2018-08-14 18:05
 **/

@Configuration
public class TemplateConfig {

    @Bean
    RestTemplate restTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(6000);
        requestFactory.setReadTimeout(6000);

        RestTemplate restTemplate = new RestTemplate(requestFactory);
        return restTemplate;
    }
}

複製代碼

 

    • 使用RestTemplate.getForEntity()方法,調用遠程發貨接口

複製代碼

 @Autowired
    private RestTemplate restTemplate;
    
    /** 
    * @Description: 獲取發貨系統返回數據
    * @Param: [url, orderid]
    * @return: java.lang.String
    * @Author: GaraYing
    * @Date: 2018/8/15 17:12
    */
    @Override
    public String invoke(String url, String orderid) {
        return restTemplate.getForEntity(url+orderid,String.class).getBody();
    }

複製代碼

    •  Service實現(sendOrderByTemplateThread方法)

     重點: 這裏做了一個判斷,對每次傳入的訂單實體,會進行一次數據庫update操作,只有第一次進入的線程纔會返回true,後續的重複請求返回false , 從而實現樂觀鎖 1 == orderMapper.updateByVersion(order)

複製代碼

/**
     * @Description: 基於狀態機的樂觀鎖
     * @Param: [order]
     * @return: java.lang.String
     * @Author: GaraYing
     * @Date: 2018/8/15 9:35
     */
    @Override
    public String sendOrderByTemplateThread(Order order) {
        String orderId = order.getOrderId();
        // 只有第一個操作返回true,其他返回false
        Boolean lock = template.execute(new TransactionCallback<Boolean>() {
            @Override
            public Boolean doInTransaction(TransactionStatus transactionStatus) {
                Order order = new Order();
                order.setOrderId(orderId);
                order.setOrderStatus("4");//訂單處理中
                order.setVersion(0);
//                orderMapper.update(order);
                return 1 == orderMapper.updateByVersion(order);//受影響的記錄數
            }
        });

        if (lock) {
            // 只允許一個線程發貨,其他全部攔截
            String flag = transService.invoke(url, orderId);
            template.execute(new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus transactionStatus) {
                    Order orderFin = new Order();
                    orderFin.setOrderId(orderId);
                    orderFin.setOrderStatus(flag);//訂單處理中
                    orderFin.setVersion(1);
//                    orderMapper.update(order);
                    orderMapper.updateByVersion(orderFin);//受影響的記錄數
                    return null;
                }
            });
        } else {
            logger.error("lockFail************" + order.getOrderId());
        }
        return null;
    }

複製代碼

    •  實體類參考

複製代碼

/**
 * @description: Order訂單實體類
 * @author: GaraYing
 * @create: 2018-08-14 10:45
 **/

public class Order {

    private String orderId; //訂單ID
    private String orderTime; // 訂單時間
    private Long orderMoney;   // 訂單金額
    private String orderStatus; //訂單狀態:0未處理/1處理中/2處理失敗/3處理成功/4處理完成
    private Integer version; // 版本

    public Order() {
    }

    public Order(String orderId, String orderTime, Long orderMoney, String orderStatus, Integer version) {
        this.orderId = orderId;
        this.orderTime = orderTime;
        this.orderMoney = orderMoney;
        this.orderStatus = orderStatus;
        this.version = version;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getOrderTime() {
        return orderTime;
    }

    public void setOrderTime(String orderTime) {
        this.orderTime = orderTime;
    }

    public Long getOrderMoney() {
        return orderMoney;
    }

    public void setOrderMoney(Long orderMoney) {
        this.orderMoney = orderMoney;
    }

    public String getOrderStatus() {
        return orderStatus;
    }

    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", orderTime='" + orderTime + '\'' +
                ", orderMoney=" + orderMoney +
                ", orderStatus='" + orderStatus + '\'' +
                ", version='" + version + '\'' +
                '}';
    }
}

複製代碼

 核心點總結:基於狀態機的樂觀鎖的實現主要利用了一下核心數據庫語句,當用戶在前端頁面,以單身狗的手速瘋狂點擊產生重複訂單的情況下,可以保證只有第一次請求會處理並進入,即完成了只有一個線程發貨,其他全部攔截

update t_order set orderStatus =#{orderStatus,jdbcType=VARCHAR},version = version+1 where orderId = #{orderId,jdbcType=VARCHAR} and version = #{version}

 因爲自己也是菜鳥一枚,在視頻的幫助下,加上了一些自己的理解,初步完成了這樣一個示例,還有很多不足和需要完善的地方,後續會更新改正,希望能幫助到需要的朋友們,有問題大家一起交流。

在能不能成爲之前,請先問自己 想不想成爲 想要成爲什麼樣的人 只要是迫切的想要成爲,那你就是可以的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章