订单管理是电商项目中的重点业务逻辑:redis
1.订单表spring
order_id 订单主键sql
username数据库
order_num 订单编号安全
payment 支付方式并发
pay_platform性能
delivery 送货方式ui
is_confirm 送货前确认电话orm
order_sum对象
ship_fee 是否付款
order_state
payment_cash 货到付款方式
distri_id 配送商id
delivery_method 送货方式
payment_no 支付号
order_time 下单时间
pay_time 付款时间
deposit_time 到帐时间
success_time 成功时间
update_time 最后修改时间 例如地址写错修改之类的
srv_type 业务类型 0 无业务 1 2 须要办理CRM业务
is_deleted 删除
is_call 是否外呼过 催付款 0 未外呼 1 已外呼
delivery_no 物流编号
is_plant 发票是否打印
收货地址等信息字段
2.订单明细
order_DETAIL_ID
order_id
item_id 商品主键
item_name
item_no 商品编号
sku_id
sku_spec 规格值
market_price
sku_price
quantity 购买数量
活动之类的信息
活动营销之类的
3.存在的问题:
(3.1)并发问题
若是客户A和客户B同时购买某一个商品的同一个skuid,A购买2个,B购买3个,若是库存是100
<1>EbSku sku = skuDao.getSkuById();
<2>sku.setStockInventory(sku.getStockInventory()-detail.getQuantity());
<3>skuDao.update(sku);
skuid stock
1001 100
A 1001(2) 98
B 1001(3) 97
假如A和B同时执行第1行代码,A和B查询上来的数据彻底相同,在相同的数据基础上来修改必定会产生并发的问题。
若是<1>加锁那么AB查出来是不一样的,也就不会产生并发问题。
若是查询的时候使用 select * from sku where sku_id = ? for update 加上for update的话会开启事务,事务挂起,若是同时另外一个窗口查询一样的sql,须要等到第一条sql提交以后才能够;因此加for update 能够解决加锁的问题,这就是herbnate的悲观锁。
解决办法:
1.)可以使用数据库的悲观锁,在第<1>行的代码的查询上加上for update会开启事务,因为spring的传播特性,每个提交订单的操做都是用的一个事务,若是A和B对同一条数据操做,当执行根据skuid查询sku对象的时候必定有一我的被阻塞在外,直到update代码执行完毕另外一个才能获得执行,这样能够保证咱们的数据安全。可是带来的问题就是性能低,对于互联网项目要求性能高的此种方式不适合。
2.)乐观锁:version控制并发的字段
skuid stock version
默认值 1001 100 1
A(2) 1001
B(3)
update所对应的sql
A:
update sku s set s.stock = 98 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1
and t.stock >2
B;
update sku s set s.stock = 97 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1
and t.stock >3
以上AB同时执行的时候,修改库存的时候因为查询上来的数据彻底相同因此执行的sql传递过来的数据也相同,A和B谁先执行有随机性,假设A先执行因为A和B对同一条数据在修改,那么B必定被阻塞在外,A提交事务后B才能获得执行,B的update的相应条数是0,至关于没有获得执行,那么B执行的条件须要作更改:
致使相应条数是0有2个缘由:
1>version 并发问题致使
2>stock致使的 可能库存不够了
update sku s set s.stock = 97 ,t.version = s.version+1 where sku_id = 1001 and t.version = 1
and t.stock >2
目前采用先查询后修改的方式:
public void saveOrder(EbOrder order,List<EbOrderDetail> detailList){
orderDao.save(order);
for(EbOrderDetail detail : detailList){]
detail.setOrderId(order.getOrderId());
orderDetailDao.saveOrderDetail(detail);
//扣减库存
EbSku sku = skuDao.getSkuById(detail.getSkuId());
sku.setStockInventory(sku.getStockInventory()-detail.getQuantity());
int flag = skuDao.update(sku);
if(flag == 0){
//一旦出现flag为0,那么就是出问题了,那么这个订单不能提交了,订单不让入库了,那么当前整个事务回滚;如何实现呢?抛出运行时异常,那么整个事务就回滚了;
//问题来了 抛出异常如何查看是version仍是stock的问题?
EbSku sku = skuDao.getSkuById(detail.getSkuId());
if(sku.getStock() < detail.getQuantity()){
throw new 自定义异常;
}else{
//并发问题引发的
throw new 并发引发的自定义异常
}
}
//redis不受事务控制 对redis中的数据进行更新处理
}
}
在外层调用saveOrder()处
try{
orderService.saveOrder(order,detailList)
}catch(Exceptione){
if(e instanceof EbStockException){
model.addAttribute("tip","stock error")
}else if(e instanceof 自定义的版本号异常){
orderService.saveOrder(order,detailList);//当版本号问题的时候从新提交就能够了,可是若是从新提交再次发生并发那依然仍是上次分析流程
}
}
分析:
其实在上述代码中能够不查询直接修改,可是在一些比较复杂的项目中,有些数据必须先查询出来处理其余业务,本项目中能够直接进行修改
若是业务不是必需要查询再修改就尽可能不要先查询后修改
skuDao.update(sku);
update sku t set t.stock = t.stock - #{quantity} where t.sku_id = #{skuId} and t.stock >= #{quantity}
此种状况一旦出现上述flag ==0 的状况 那必定是stock库存的问题
订单的流程