在騰訊課堂聽了一節公開課,學習到了這麼一個項目,遂記錄下來。前端
發貨系統和訂單系統基於Spring-Boot項目,其中springboot整合了mybatis,log4j2等 ,項目中使用到了generator代碼生成工具,生成dao/domain/mapper.xml文件java
/** * @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"; } }
<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>
/** * @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); } } }
/** * @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; } }
@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(); }
重點: 這裏作了一個判斷,對每次傳入的訂單實體,會進行一次數據庫update操做,只有第一次進入的線程纔會返回true,後續的重複請求返回false , 從而實現樂觀鎖 1 == orderMapper.updateByVersion(order)spring
/** * @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}
由於本身也是菜鳥一枚,在視頻的幫助下,加上了一些本身的理解,初步完成了這樣一個示例,還有不少不足和須要完善的地方,後續會更新改正,但願能幫助到須要的朋友們,有問題你們一塊兒交流。springboot