秒杀之交易模型

本文介绍了如何设计和实现秒杀系统的交易模型,从用户下单流程开始,讨论了OrderModel的设计,订单与交易模型的关系,以及如何处理商品价格变动。接着,详细讲述了数据库表的构建,MyBatis-Generator的使用,库存扣减服务及其实现,以及加锁机制确保库存操作的原子性。此外,还提到了序列号生成表sequence_info的创建和使用,以及在SpringBoot中遇到的问题和解决方案。
摘要由CSDN通过智能技术生成

1.商品详情页最终目的是为了完成用户下单

一个普通用户的下单过程

先考虑交易模型,而不是数据库

建立OrderModel.java 用来解决用户下单的交易模型

import java.math.BigDecimal;

//用户下单的交易模型
public class OrderModel {
    //订单号 有属性
    private String id;

    //购买用户的id
    private Integer userId;

    //购买的商品id
    private Integer itemId;

    //购买商品的单价
    private BigDecimal itemPrice;

    //购买数量
    private Integer amount;

    //购买金额
    private BigDecimal orderAmount;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getItemId() {
        return itemId;
    }

    public void setItemId(Integer itemId) {
        this.itemId = itemId;
    }

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public BigDecimal getOrderAmount() {
        return orderAmount;
    }

    public void setOrderAmount(BigDecimal orderAmount) {
        this.orderAmount = orderAmount;
    }

    public BigDecimal getItemPrice() {
        return itemPrice;
    }

    public void setItemPrice(BigDecimal itemPrice) {
        this.itemPrice = itemPrice;
    }
}

一个订单就是交易模型的一部分

id 类型为String :一般企业的订单编号有一定的属性 比如说2018年21日 下单 +编号

购买总金额=商品数量×购买商品的单价 但是商品单价在变化  

购买之后 商品单价发生变化 但是买过后的总金额不变

2.对应数据库的建立

3.在mybatis-generator中添加表并生成对应的Mapper、Dao文件

<table tableName="item_stock" domainObjectName="ItemStockDO"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"></table>

 4.创建一个OrderService接口 用来处理订单

import com.miaoshaproject.service.model.OrderModel;

public interface OrderService {
    OrderModel createOrder(Integer userId, Integer itemId, Integer amount);
}

5.创建实现OrderService的OrderServiceImpl

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private SequenceDOMapper sequenceDOMapper;

    @Autowired
    private ItemService itemService;

    @Autowired
    private UserService userService;

    @Autowired
    private OrderDOMapper orderDOMapper;

    @Override
    //保证创建订单在同一个事务当中
    @Transactional
    public OrderModel createOrder(Integer userId, Integer itemId, Integer amount) throws BusinessException {
        //1.校验下单状态,下单的商品是否存在,用户是否合法,购买数量是否正确
        ItemModel itemModel = itemService.getItemById(itemId);
        if(itemModel == null){
            throw  new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"商品信息不存在");
        }

        UserModel userModel =userService.getUserById(userId);
        if(userModel == null){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"用户信息不存在");
        }
        //下单数量假设为不能小于0 不能大于99
        if(amount <=0 || amount >99){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"购买的数量信息不正确");
        }

 // 2.落单减库存:在调用订单的createorder下单之前,先把amount数量的库存锁定给这个用户使用,若库存不够就为下单失败。下单成功则锁定库存的操作必定成功
 //  支付减库存(未使用)
           boolean result = itemService.decreaseStock(itemId,amount);
           if(!result){
               throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
           }

        //3.订单入库
            OrderModel orderModel = new OrderModel();
            orderModel.setUserId(userId);
            orderModel.setItemId(itemId);
            orderModel.setAmount(amount);
            orderModel.setItemPrice(itemModel.getPrice());
            orderModel.setOrderPrice(itemModel.getPrice().multiply(new BigDecimal(amount)));
           //生成交易流水号--订单号
            orderModel.setId(generateOrderNo());
            OrderDO orderDO = convertFromOrderModel(orderModel);
           orderDOMapper.insertSelective(orderDO);

           //加上商品销量
        itemService.increaseSales(itemId,amount);
        //4.返回前端
        return orderModel;
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private String generateOrderNo(){
        //订单号有16位
        StringBuilder stringBuilder = new StringBuilder();
        //前八位为时间信息 年月日
        //获取今天日期
        LocalDateTime now = LocalDateTime.now();
        String nowDate= now.format(DateTimeFormatter.ISO_DATE).replace("-","");
        stringBuilder.append(nowDate);

        //中间六位为自增序列
        //获取当前sequence
        int sequence = 0;
        SequenceDO sequenceDO = sequenceDOMapper.getSequenceByName("order_info");

        sequence =sequenceDO.getCurrentValue();
        sequenceDO.setCurrentValue(sequenceDO.getCurrentValue() + sequenceDO.getStep());
        sequenceDOMapper.updateByPrimaryKeySelective(sequenceDO);

        //拼接对应的sequence 凑够六位
        String sequenceStr = String.valueOf(sequence);
        for(int i =0; i < 6-sequenceStr.length();i++){
            stringBuilder.append(0);
        }
         stringBuilder.append(sequenceStr);

        //最后两位为分库分表为1-99 暂时写死
        stringBuilder.append("00");

        return stringBuilder.toString();
    }

    //将Model转为为一个dataobject的方式
    private OrderDO convertFromOrderModel(OrderModel orderModel){
         if(orderModel == null){
             return null;
         }
         OrderDO orderDO =new OrderDO();
        BeanUtils.copyProperties(orderModel,orderDO);
        orderDO.setItemPrice(orderModel.getItemPrice().doubleValue());
        orderDO.setOrderPrice(orderModel.getOrderPrice().doubleValue());
        return orderDO;
    }
}

6.ItemService实现库存扣减

//库存扣减
boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException;

7.ItemServiceImpl实现库存扣减

@Override
    @Transactional
    public boolean decreaseStock(Integer itemId, Integer amount) throws BusinessException {
        //影响的条目数 affectedRow
        int affectedRow = itemStockDOMapper.decreaseStock(itemId, amount);
        if (affectedRow > 0) {
            //更新库存成功
            return true;
        } else {
            //更新库存失败
            return false;
        }
}

8.ItemStockDOMapper.xml中添加减去库存的方法 

当商品id对应的这个商品的库存 大于等于 要扣减的数量的时候 执行操作

<update id="decreaseStock">
    update item_stock
    set stock = stock-#{amount}
    where item_id = #{itemId} and stock >= #{amount}
  </update>

10.ItemStockDOMapper中添加语句

    int decreaseStock(@Param("itemId") Integer itemId, @Param("amount") Integer amount);

9.保证冻结操作的原则性 对item_stock加锁 

   如对item_id =1的商品加锁  看减完后的库存是否大于0

对item表 没有任何影响

10.建一张通用的表sequence_info

初始化一些sequence 初始值0  获取一次的sequence 就加上一定的步长

mybatis-generator中添加对应的信息

 <table tableName="sequence_info" domainObjectName="SequenceDO"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false"
               enableSelectByExample="false"
               selectByExampleQueryId="false"></table>

 11.在SequenceDOMapper.xml中添加语句

<select id="getSequenceByName" resultMap="BaseResultMap" parameterType="java.lang.String" >
    select
    <include refid="Base_Column_List" />
    from sequence_info
    where name = #{name,jdbcType=VARCHAR} for update
  </select>

12.在SequenceDOMapper.java中添加方法

 SequenceDO getSequenceByName(String name);

13.修改getitem.html

<html>
<head>
    <meta charset="UTF-8">
    <link href="static/assets/global/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <link href="static/assets/global/css/components.css" rel="stylesheet" type="text/css"/>
    <link href="static/assets/admin/pages/css/login.css" rel="stylesheet" type="text/css"/>
    <script src="static/assets/global/plugins/jquery-1.11.0.min.js" type="text/javascript"></script>
    <title>Title</title>
</head>
<body class="login">
<div class="content">
    <h3 class="form-title">商品详情</h3>
    <div class="form-group">
        <label class="control-label">商品名</label>
        <div>
            <label class="control-label" id="title" />
        </div>
    </div>
    <div class="form-group">
        <label class="control-label">商品描述</label>
        <div>
            <label class="control-label" id="description" />
        </div>
    </div>
    <div class="form-group">
        <label class="control-label">商品价格</label>
        <div>
            <label class="control-label" id="price" />
        </div>
    </div>
    <div class="form-group">
        <label class="control-label">图片</label>
        <div>
            <img style="width: 200px;height: auto" id="imgUrl"/>
        </div>
    </div>
    <div class="form-group">
        <label class="control-label">商品库存</label>
        <div>
            <label class="control-label" id="stock" />
        </div>
    </div>

    <div class="form-group">
        <label class="control-label">商品销量</label>
        <div>
            <label class="control-label" id="sales" />
        </div>
        <div class="form-actions">
            <button class="btn blue" id="createorder" type="submit">
                下单
            </button>
        </div>
    </div>

</div>
</body>

<script>
    function getParam(paramName) {
        paramValue = "", isFound = !1;
        if (this.location.search.indexOf("?") == 0 && this.location.search.indexOf("=") > 1) {
            arrSource = unescape(this.location.search).substring(1, this.location.search.length).split("&"), i = 0;
            while (i < arrSource.length && !isFound)
                arrSource[i].indexOf("=") > 0 && arrSource[i].split("=")[0].toLowerCase() == paramName.toLowerCase() && (paramValue = arrSource[i].split("=")[1], isFound = !0), i++
        }
        return paramValue == "" && (paramValue = null), paramValue
    }
    var g_itemVO = {};
    jQuery(document).ready(function () {
        $("#createorder").on("click",function () {
            $.ajax({
                type:"POST",
                contentType:"application/x-www-form-urlencoded",
                url:"http://localhost:8090/order/createorder",
                data:{
                    "itemId":g_itemVO.id,
                    "amount":1,

                },
                //允许跨域请求
                xhrFields:{withCredentials:true},
                success:function (data) {
                    if (data.status=="success") {
                      alert("下单成功");
                      window.location.reload();
                    }else {
                        alert("下单失败,原因为" + data.data.errMsg);
                        if(data.data.errCode == 20003){
                            window.location.href="login.html"
                        }
                    }
                },
                error:function (data) {
                    alert("下单失败,原因为"+data.responseText);
                }
            });
        })
            //获取商品详情
            $.ajax({
                type:"GET",
                contentType:"application/x-www-form-urlencoded",
                url:"http://localhost:8090/item/get",
                data:{
                    "id":getParam("id"),

                },
                //允许跨域请求
                xhrFields:{withCredentials:true},
                success:function (data) {
                    if (data.status=="success") {
                        g_itemVO = data.data;
                        reloadDom();
                    }else {
                        alert("获取信息失败,原因为" + data.data.errMsg);
                    }
                },
                error:function (data) {
                    alert("获取信息失败,原因为"+data.responseText);
                }
            });
        });
    function reloadDom() {
        $("#title").text(g_itemVO.title);
        $("#imgUrl").attr("src", g_itemVO.imgUrl);
        $("#description").text(g_itemVO.description);
        $("#price").text(g_itemVO.price);
        $("#stock").text(g_itemVO.stock);
        $("#sales").text(g_itemVO.sales);
    }
</script>
</html>

14.创建OrderController

⚠ 运行了一下 报错

解决:springboot 启动报错Field XXX required a bean of type XXX that could not be found._Julycaka的博客-CSDN博客没有在OrderServiceImpl 加@Service

 

 15.在ItemService

    //商品销量增加
    void increaseSales(Integer itemId,Integer amount) throws BusinessException;

16.在ItemServiceImpl

   @Override
    @Transactional
    public void increaseSales(Integer itemId, Integer amount) throws BusinessException {
      itemDOMapper.increaseSales(itemId,amount);
    }

17.在ItemDOMapper.xml中添加sql语句

<update id="increaseSales">
  update item
  set sales = sales+ #{amount}
  where id = #{id,jdbcType=INTEGER}
</update>

18.ItemDOMapper.java

int increaseSales(@Param("id") Integer id, @Param("amount") Integer amount);

最后运行结果为

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值