电商项目业务逻辑-3 订单管理悲观锁和乐观锁

订单管理是电商项目中的重点业务逻辑: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库存的问题

 

订单的流程

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息