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

订单管理是电商项目中的重点业务逻辑:

 1.订单表

order_id 订单主键

username

order_num 订单编号

payment 支付方式

pay_platform

delivery 送货方式

is_confirm 送货前确认电话

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库存的问题

订单的流程

猜你喜欢

转载自www.cnblogs.com/healthinfo/p/9682419.html