Preparation:
Design table structure:
Product Table
Purchase Records list
dao design
<!--获取产品--> <select id="getProduct" parameterType="long" resultType="product"> select id,product_name as productName, stock,price,version,note from t_product where id=#{id} </select> <!--减少库存--> <update id="decreaseProduct"> update t_product set stock = stock - #{quantity} where id = #{id} </update>
<!--插入购买记录--> <insert id="insertPurchaseRecord" parameterType="purchaseRecord"> insert into t_purchase_record( user_id,product_id,price,quantity,sum,purchase_date,note) values(#{userId},#{productId},#{price},#{quantity}, #{sum},now(),#{note}) </insert>
service layer design
The @Transactional public boolean Purchase (userId Long, Long productId, the Quantity int) { // get the product ProductPo = productMapper.getProduct Product (productId); // compare inventory and purchases IF (product.getStock () <the Quantity) { return false ; } // deductions stock productMapper.decreaseProduct (productId, the Quantity); // create the purchase record PurchaseRecordPo PR = this.initPurchaseRecord (userId, Product, the Quantity); // insert purchase records purchaseRecordMapper.insertPurchaseRecord (PR); return to true; } Private PurchaseRecordPo initPurchaseRecord (Long the userId, ProductPo Product, Quantity int) { PurchaseRecordPo new new PurchaseRecordPo PR = (); pr.setNote("购买日志,时间:" + System.currentTimeMillis()); pr.setProductId(product.getId()); pr.setPrice(product.getPrice()); pr.setQuantity(quantity); double sum = product.getPrice() * quantity; pr.setSum(sum); pr.setUserId(userId); return pr; }
controller layer design
@GetMapping("/purchase") public String purchase(){ return "purchase"; } @PostMapping("/purchase") @ResponseBody public Result purchase(Long userId,Long productId,Integer quantity){ boolean success = purchaseService.purchase(userId,productId,quantity); String message = success? "抢购成功":"抢购失败"; Result result = new Result(success,message); return result; } class Result{ private boolean success; private String message; public Result() { } public Result(boolean success, String message) { this.success = success; this.message = message; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
The front page calls
<script type="text/javascript" src="/js/jquery-1.8.3.js"></script> <script type="text/javascript"> for(var i=0;i<50000;i++){ var params = { userId:1, productId:1, quantity:1 }; $.post("/purchase/purchase",params,function (result) { //alert(result.message); }) } </script>
Fifty thousand grab twenty thousand commodities results are as follows
Super phenomenon occurs can be seen, sold out 20,003 of goods, inventory becomes -3
So the question should be how to solve it? The current enterprise proposes pessimistic locking, optimistic locking, redis and other solutions
First, pessimistic locking
<!--获取产品--> <select id="getProduct" parameterType="long" resultType="product"> select id,product_name as productName, stock,price,version,note from t_product where id=#{id} for update </select>
<!--减少库存--> <update id="decreaseProduct"> update t_product set stock = stock - #{quantity}, version = version + 1 where id = #{id} and version = #{version} </update>
别忘记去掉for update,并同步修改dao减少库存接口加上第三个参数
@Transactional public boolean purchase(Long userId, Long productId, int quantity) { // 获取产品 ProductPo product = productMapper.getProduct(productId); // 比较库存与购买量 if (product.getStock()<quantity){ return false; } // 获取当前版本号 int version = product.getVersion(); // 尝试扣减库存 int result = productMapper.decreaseProduct(productId, quantity, version); if (result == 0) { return false; } // 创建购买记录 PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity); // 插入购买记录 purchaseRecordMapper.insertPurchaseRecord(pr); return true; }
结果如下:卖出5447,剩余14553件产品
这次时间是快了,但是却剩余了大量的产品,为什么呢,因为并发操作时好多操作都被判定失败了。
改进方法:
public boolean purchase(Long userId, Long productId, int quantity) { // 当前时间 long start = System.currentTimeMillis(); // 循环尝试直到成功 while(true){ // 循环时间 long end = System.currentTimeMillis(); if (end - start>100){ return false; } // 获取产品 ProductPo product = productMapper.getProduct(productId); // 比较库存与购买量 if (product.getStock()<quantity){ return false; } // 获取当前版本号 int version = product.getVersion(); // 尝试扣减库存 int result = productMapper.decreaseProduct(productId, quantity, version); if (result == 0) { continue; } // 创建购买记录 PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity); // 插入购买记录 purchaseRecordMapper.insertPurchaseRecord(pr); return true; } }
@Transactional public boolean purchase(Long userId, Long productId, int quantity) { for(int i=0;i<3;i++){ // 获取产品 ProductPo product = productMapper.getProduct(productId); // 比较库存与购买量 if (product.getStock()<quantity){ return false; } // 获取当前版本号 int version = product.getVersion(); // 尝试扣减库存 int result = productMapper.decreaseProduct(productId, quantity, version); if (result == 0) { continue; } // 创建购买记录 PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity); // 插入购买记录 purchaseRecordMapper.insertPurchaseRecord(pr); return true; } return false; }
结果如下:这次还好,剩余6550,卖出13450,耗时1分16秒
三、那有没有更好的方法呢,有的那就是使用redis
采用lua脚本在内存中操作数据进行抢购,再通过定时任务的方式将其写入到数据库中,总用时15秒,非常快。