多线程下spring事务相关问题

烟花苏柳 2017-09-21 09:00:13
商城APP后台开发 过程中,发现一个多线程引起的问题 :
问题接口:,用户订单预览接口
接口入参:商品编号、数量
service层处理方法:preOrder()
preOrder方法底层处理 :
1.比较商品库存和下单数量,如果库存足够,执行第二步操作
2.往订单表中插入数据,注意将订单状态置为:待付款
3.减少商品库存,增加商品销量
4.返回订单信息、订单中的商品信息
代码层:
@Override
public ResponseContent orderProview(String userId, List<IdAndNum> list) {
Integer itemsSum = 0; // 商品总数
if (CollectionUtils.isEmpty(list)) {
throw new ErrorOperationException("订单生成失败!请确认商品:["+list+"] 是否存在!");
}
List<String> ids = new ArrayList<>(); // 用于存储请求购买的商品编号集合
Map<String, Integer> map = new HashMap<>(); // 使用Map来储存商品编号、购买数量
for (IdAndNum idAndNum : list) {
itemsSum += idAndNum.getNum(); // 记录商品总数
ids.add(idAndNum.getId()); // 记录用户购买的商品编号
map.put(idAndNum.getId(), idAndNum.getNum());// key=商品编号, value=购买数量
}
System.out.println("ids : " + ids);
// 2.创建Example查询
Example example = new Example(ShopItems.class); // ShopItems.java : 商品类
example.createCriteria().andIn("itemId", ids); // 查询自定商品编号范围内的商品数据
// 3.通过商品编号集合查询所有的商品数据
List<ShopItems> items = itemsMapper.selectByExample(example);
if (CollectionUtils.isEmpty(items)) {
return null;
}
// 4.遍历查询到的商品数据
for (int i = 0; i < items.size(); i++) {
String itemId = items.get(i).getItemId(); // 拿到商品编号
// 当库存为0 或 库存小于购买量时,抛出异常
if (items.get(i).getNum() <= 0 || map.get(itemId) > items.get(i).getNum()) {
throw new DataBaseException("订单生成失败,因为编号为 [" + itemId +
"] 的商品仅剩库存为" + items.get(i).getNum()+"件了");
}
list.remove(itemId); // 将商品
}
if (list == null || list.size() <= 0) { // 未删除的 商品即为查询不出来的商品
// 如果list集合中还有数据,抛出异常信息
throw new ErrorOperationException("商品 : " + list + "不能购买,请确认这些商品是否已经下架!");
}

BigDecimal totalMoney = new BigDecimal("0"); // 计算商品的总价
// 销量++,库存--
for (ShopItems item : items) {
Integer num = map.get(item.getItemId());

Example e2 = new Example(ShopItems.class);
e2.createCriteria().andEqualTo("itemId", item.getItemId());
// 销量++
item.setVolume(item.getVolume() + num);
// 库存--
item.setNum(item.getNum() - num);
// item.setVersion(item.getVersion()+1); // 版本号++...
itemsMapper.updateByExampleSelective(item,e2);
totalMoney.add(item.getCurrPrice());
}

// 往订单表中插入数据
ShopOrders orders = new ShopOrders();
orders.setCreateTime(new Date());
orders.setStatus((byte) 1); // 1:待付款
orders.setuId(userId);
orders.setPayment(totalMoney);
String orderId = UUIDUtils.getUUID();
orders.setOrderId(orderId);
int i = ordersMapper.insertSelective(orders);
if (i <= 0) throw new DataBaseException("订单数据生存失败!");

// 往订单商品中间表中插入数据
for (int j = 0; j < items.size(); j++) {
ShopOrdersItemsMid orderItems = new ShopOrdersItemsMid();
orderItems.setOrdersItemMinId(UUIDUtils.getUUID()); // 编号
orderItems.setItemId(items.get(j).getItemId()); // 商品编号
orderItems.setNum(map.get(items.get(j).getItemId())); // 商品数量
orderItems.setOrderId(orderId); // 订单号
orderItems.setItemName(items.get(j).getName()); // 商品名
orderItems.setPrice(items.get(j).getCurrPrice()); // 商品单价
orderItems.setuId(userId);
// 执行插入操作
midMapper.insertSelective(orderItems);
}
// 返回数据封装
PreSettlement settlement = new PreSettlement(); // PreSettlement.java : 封装了商品总数,订单总价,商品集合等等数据。。。
// 商品总个数
settlement.setItemsSum(itemsSum);
// 订单号
settlement.setOrderId(orderId);
// 商品总价
settlement.setTotalMoney(totalMoney);
// 商品集合
settlement.setItemsList(items);
return ResponseUtil.okContent(settlement, null);
}


问题描述:
1.某件商品库存仅剩4件,这时两个用户同时下单,都是购买3件商品,但他们获取到的库存都是4件,所以都执行了第二步操作(订单表中插入数据)
2.这就相当于生成了6件商品的订单....
这该怎么破?
还有,我的代码多的有点离谱,有什么优化建议欢迎指正!



...全文
314 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
烟花苏柳 2017-09-29
  • 打赏
  • 举报
回复
多谢大家了,最后决定使用乐观锁
yue_hu 2017-09-22
  • 打赏
  • 举报
回复
这种多线程的威胁主要是由于查数据是一个耗时的过程(相对于一行代码),线程转移多出现在一个线程在查数据阶段等待数据库返回响应时间。可以在更新商品数量之后保存数量到缓存中(可以在订单压力小时间做一下缓存和数据库的同步),然后在操作更新数据库数据之前去缓存中获取商品数量进行辨别。从缓存中获取数据的速度是优于数据库的。这样可以大大避免这种线程威胁,但并不能完全避免。
xqchenxue2 2017-09-22
  • 打赏
  • 举报
回复
建议订单和减库存分两个单据处理,而不知直接生成订单的时候就减库存,另外库存或批次加一个申请数量,订单操作申请数量,出库才正真减数量
北飞的企鹅 2017-09-22
  • 打赏
  • 举报
回复
表加上锁不就好了嘛

81,092

社区成员

发帖
与我相关
我的任务
社区描述
Java Web 开发
社区管理员
  • Web 开发社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧