mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况
=============================================================
完整的代码请到GIthub查看:
多个线程处理完后再做事情:
=============================================================
先说说同一个事务中使用一个乐观锁的情况:
核心功能点:
1.先做查询 【查询时候把version带出来】
2.再做更新【更新的时候判断version是不是查出来时候的version,如果是,则更新,更新时顺便version+1即可。否则不更新】
update goods_stock set stock = stock - #{buyNum}, sale_num = sale_num + #{buyNum}, version = version + 1 where uid = #{uid} and version = #{version}
=============================================================
1.实体对应数据表
/** * 低配版本的 商品库存表 */@Entity@Table@Getter@Setterpublic class GoodsStock extends BaseBean { private String goodsName;//商品名称 private String goodsPrice;//商品价格 private Long buyNum;//购买数量 private Long saleNum;//销售量 private Long stock;//商品库存 库存为-1 代表无限量库存 private Integer version;//版本号 @Transient private Integer threadCount;//模拟并发访问的线程数量 实际业务中不用这个字段 仅用作本次测试接口使用}
2.mybatis的mapper.xml
update goods_stock set stock = stock - #{buyNum}, sale_num = sale_num + #{buyNum}, version = version + 1 where uid = #{uid} and version = #{version}
mybatis的mapper.java
package com.sxd.swapping.dao.mybatis;import com.sxd.swapping.domain.GoodsStock;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;@Mapperpublic interface GoodsStockMapper { int updateStock(GoodsStock goodsStock); GoodsStock findByUid(@Param("uid") String uid);}
3.serviceImpl层代码
@Autowired GoodsStockMapper mapper; /** * 数据库加 version 版本号 * * 实现 数据库乐观锁 * * 实现高并发下库存的并发控制机制 * * 要保证事务一致性,要么都使用mybatis 要么都使用jpa * @param map * @param entity * @param threadNum * @return */ @Override @Transactional public void updateStock(Mapmap, GoodsStock entity, Integer threadNum) { String uid = entity.getUid(); Long buyNum = entity.getBuyNum(); String msg = ""; //判断库存是否足够 GoodsStock old = mapper.findByUid(uid); Long stock = old.getStock(); System.out.println("线程"+threadNum+"---------->正在工作"); if (stock >= buyNum){ old.setBuyNum(buyNum); if (mapper.updateStock(old) > 0 ){ msg = "库存扣除成功,剩余库存数量:"; }else { msg = "库存扣除失败,剩余库存数量:"; } Long nowStock = mapper.findByUid(uid).getStock(); msg +=nowStock; }else { msg = "库存不足,剩余库存数量:"+stock; } map.put(threadNum,msg); }
4.controller层代码:
/** * uid代表 同一时间 大家都来买这一件东西 * threadCount代表 同时会有多少人在操作 * buyNum代表 同一个人的一次购买量 * @param entity * @return */ @RequestMapping(value = "/concurrentStock",method = RequestMethod.POST) public UniVerResponse
5.发送请求
第一次请求:
下图所示,仅有线程编号为3的 线程 购买成功,其他都购买失败。
第二次请求:
第三次请求:
第四次请求:
最后一次请求:
二.再说说在同一个事务中使用多个乐观锁的情况
===============================================================================================
下面仅写一段代码举个例子即可:
即 第一步操作,第二步 都会使用乐观锁
如果执行失败有两种情况:
1.数据库连接断开,sql真正的执行出错
2.sql成功执行,但是其实update执行失败,因为version对应不起来
所以需要注意的是 如果使用乐观锁执行失败[失败情况2],那么需要自己手动去抛出异常,去保证事务的一致性!!!
因为失败情况1自己会抛出RuntimeException
因为下面示例代码中的第一步操作如果失败了会直接返回 所以并没有去抛异常
/** * 进行兑换 * * 1.减少会员积分总数[加乐观锁] * * 2.减少商品库存 增加商品销量[加乐观锁] * * 3.新增兑换记录 * * * @param entity * @return */ @Override @Transactional public boolean insert(ExchangeOrder entity,String integralUid,Integer buyIntegral) { boolean isSuccess = false; //1.减少会员积分 IntegralDetail integralDetail = integralDetailMapper.findByIntegralId(integralUid); integralDetail.setIntegralValue(buyIntegral);//sql 做减操作 isSuccess = (integralDetailMapper.deductIntegral(integralDetail) > 0); if (isSuccess){ //2.减少商品库存 增加商品销量 IntegralGoods integralGoods = integralGoodsMapper.findByUid(entity.getIntegralGoodsId()); //无限库存不做修改 if (integralGoods.getStock() != -1) { integralGoods.setStock(entity.getBuyNum()); } //增加销量 integralGoods.setSaleNum(entity.getBuyNum()); integralGoods.initUpdateDataMen(); isSuccess = (integralGoodsMapper.updateStock(integralGoods) > 0); if (isSuccess){ //3.新增兑换记录 mapper.insert(entity); }else{ throw new RunException("销量增加失败,请稍后再试"); } } return isSuccess; }