GARA
博闻强记 多思多问 取法乎上 持之以恒

  在腾讯课堂听了一节公开课,学习到了这么一个项目,遂记录下来。

  发货系统和订单系统基于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}

 因为自己也是菜鸟一枚,在视频的帮助下,加上了一些自己的理解,初步完成了这样一个示例,还有很多不足和需要完善的地方,后续会更新改正,希望能帮助到需要的朋友们,有问题大家一起交流。

posted on 2018-08-15 17:17  GaraYing  阅读(979)  评论(2编辑  收藏  举报