1.如果 你是用下面两个步骤,查库存和判断库存,如果有多个线程,那么肯定是不行的
// 1. 查询库存
// int stock = 10;
// 2. 判断库存,是否能够减少到0以下
// if (stock - buyCounts < 0) {
// 提示用户库存不够
// 10 - 3 -3 - 5 = -1
// }
2.我们学过多线程后,会使用 synchronized(可以解决问题),但是在不推荐使用,因为在集群下无用,性能低下
3. 锁数据库: 不推荐,因为会导致数据库性能低下
4. 推荐使用 分布式锁 zookeeper redis,但是我现在不会(以后会使用)
5.那么 我只能使用乐观锁了(现在是单体项目):mybatis的xml文件
=============================================
<update id="decreaseItemSpecStock">
update
items_spec
set
stock = stock - #{pendingCounts}
where
id = #{specId}
and
stock >= #{pendingCounts}
</update>
=============================================
对应的mapper:
public int decreaseItemSpecStock(@Param("specId") String specId,
@Param("pendingCounts") int pendingCounts);
=============================================
如果 stock 不大于或等于 pendingCounts ,就执行不成功,然后 会返回 0(成功返回 1),然后就会回滚(一定要加事务):
对应的impl
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void decreaseItemSpecStock(String specId, int buyCounts) {
int result = itemsMapperCustom.decreaseItemSpecStock(specId, buyCounts);
if (result != 1) {
throw new RuntimeException("订单创建失败,原因:库存不足!");
}
}
=============================================
在创建订单的impl类上也加上事务,所以 如果 stock 不大于或等于 pendingCounts,整个 impl就会回滚,订单创建失败,
就不会超卖了
===================================================
测试一下:
订单是 3件
数据库是2件:
数据库中的订单表已全部删除:
并且该页面没有进行跳转;
看一下创建订单的impl:(主要看2.4 ,注意:不能再2.4上创建try..catch有任何异常会从业务方法中抛出,全被捕获并“吞掉”,导致spring异常抛出触发事务回滚策略失效)
@Transactional(propagation = Propagation.REQUIRED)
@Override
public String createOrder(SubmitOrderBO submitOrderBO) {
String userId = submitOrderBO.getUserId();
String addressId = submitOrderBO.getAddressId();
String itemSpecIds = submitOrderBO.getItemSpecIds();
Integer payMethod = submitOrderBO.getPayMethod();
String leftMsg = submitOrderBO.getLeftMsg();
// 包邮费用设置为0
Integer postAmount = 0;
String orderId = sid.nextShort();
UserAddress address = addressService.queryUserAddres(userId, addressId);
// 1. 新订单数据保存
Orders newOrder = new Orders();
newOrder.setId(orderId);
newOrder.setUserId(userId);
newOrder.setReceiverName(address.getReceiver());
newOrder.setReceiverMobile(address.getMobile());
newOrder.setReceiverAddress(address.getProvince() + " "
+ address.getCity() + " "
+ address.getDistrict() + " "
+ address.getDetail());
// newOrder.setTotalAmount();
// newOrder.setRealPayAmount();
newOrder.setPostAmount(postAmount);
newOrder.setPayMethod(payMethod);
newOrder.setLeftMsg(leftMsg);
newOrder.setIsComment(YesOrNo.NO.type);
newOrder.setIsDelete(YesOrNo.NO.type);
newOrder.setCreatedTime(new Date());
newOrder.setUpdatedTime(new Date());
// 2. 循环根据itemSpecIds保存订单商品信息表
String itemSpecIdArr[] = itemSpecIds.split(",");
Integer totalAmount = 0; // 商品原价累计
Integer realPayAmount = 0; // 优惠后的实际支付价格累计
for (String itemSpecId : itemSpecIdArr) {
// TODO 整合redis后,商品购买的数量重新从redis的购物车中获取
int buyCounts = 1;
// 2.1 根据规格id,查询规格的具体信息,主要获取价格
ItemsSpec itemSpec = itemService.queryItemSpecById(itemSpecId);
totalAmount += itemSpec.getPriceNormal() * buyCounts;
realPayAmount += itemSpec.getPriceDiscount() * buyCounts;
// 2.2 根据商品id,获得商品信息以及商品图片
String itemId = itemSpec.getItemId();
Items item = itemService.queryItemById(itemId);
String imgUrl = itemService.queryItemMainImgById(itemId);
// 2.3 循环保存子订单数据到数据库
String subOrderId = sid.nextShort();
OrderItems subOrderItem = new OrderItems();
subOrderItem.setId(subOrderId);
subOrderItem.setOrderId(orderId);
subOrderItem.setItemId(itemId);
subOrderItem.setItemName(item.getItemName());
subOrderItem.setItemImg(imgUrl);
subOrderItem.setBuyCounts(buyCounts);
subOrderItem.setItemSpecId(itemSpecId);
subOrderItem.setItemSpecName(itemSpec.getName());
subOrderItem.setPrice(itemSpec.getPriceDiscount());
orderItemsMapper.insert(subOrderItem);
// 2.4 在用户提交订单以后,规格表中需要扣除库存
itemService.decreaseItemSpecStock(itemSpecId, buyCounts);
}
newOrder.setTotalAmount(totalAmount);
newOrder.setRealPayAmount(realPayAmount);
ordersMapper.insert(newOrder);
// 3. 保存订单状态表
OrderStatus waitPayOrderStatus = new OrderStatus();
waitPayOrderStatus.setOrderId(orderId);
waitPayOrderStatus.setOrderStatus(OrderStatusEnum.WAIT_PAY.type);
waitPayOrderStatus.setCreatedTime(new Date());
orderStatusMapper.insert(waitPayOrderStatus);
return orderId;
}