【mysql】mysql增加version字段實現樂觀鎖,實現高並發下的訂單庫存的並發控制,通過開啟多線程同時處理模擬多個請求同時到達的情況 + 同一事務中使用多個樂觀鎖的情況處理


mysql增加version字段實現樂觀鎖,實現高並發下的訂單庫存的並發控制,通過開啟多線程同時處理模擬多個請求同時到達的情況

=============================================================

完整的代碼請到GIthub查看:https://github.com/AngelSXD/swapping

多個線程處理完后再做事情:https://www.cnblogs.com/sxdcgaq8080/p/9456006.html

=============================================================

先說說同一個事務中使用一個樂觀鎖的情況:

核心功能點:

1.先做查詢 【查詢時候把version帶出來】

<select id="findByUid" parameterType="String" resultType="com.sxd.swapping.domain.GoodsStock">
        select
        uid uid,
        version version,
        sale_num saleNum,
        stock stock
        from
        goods_stock
        where
        uid = #{uid}

    </select>

 

2.再做更新【更新的時候判斷version是不是查出來時候的version,如果是,則更新,更新時順便version+1即可。否則不更新】

    <update id="updateStock" parameterType="com.sxd.swapping.domain.GoodsStock">
        update
        goods_stock
        set
        <if test="stock != -1">
            stock = stock - #{buyNum},
        </if>
        sale_num = sale_num + #{buyNum},
        version  = version + 1
        where
        uid = #{uid}
        and
        version = #{version}
    </update>

 

=============================================================

 

1.實體對應數據表

/**
 * 低配版本的 商品庫存表
 */
@Entity
@Table
@Getter
@Setter
public class GoodsStock  extends BaseBean {

    private String goodsName;//商品名稱

    private String goodsPrice;//商品價格

    private Long buyNum;//購買數量

    private Long saleNum;//銷售量

    private Long stock;//商品庫存       庫存為-1  代表無限量庫存

    private Integer version;//版本號

    @Transient
    private Integer threadCount;//模擬並發訪問的線程數量 實際業務中不用這個字段  僅用作本次測試接口使用

}
View Code

 

 

2.mybatis的mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sxd.swapping.dao.mybatis.GoodsStockMapper">

    <update id="updateStock" parameterType="com.sxd.swapping.domain.GoodsStock">
        update
        goods_stock
        set
        <if test="stock != -1">
            stock = stock - #{buyNum},
        </if>
        sale_num = sale_num + #{buyNum},
        version  = version + 1
        where
        uid = #{uid}
        and
        version = #{version}
    </update>


    <select id="findByUid" parameterType="String" resultType="com.sxd.swapping.domain.GoodsStock">
        select
        uid uid,
        version version,
        sale_num saleNum,
        stock stock
        from
        goods_stock
        where
        uid = #{uid}

    </select>

</mapper>
View Code

mybatis的mapper.java

package com.sxd.swapping.dao.mybatis;

import com.sxd.swapping.domain.GoodsStock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface GoodsStockMapper {

    int updateStock(GoodsStock goodsStock);

    GoodsStock findByUid(@Param("uid") String uid);
}
View Code

 

3.serviceImpl層代碼

    @Autowired
    GoodsStockMapper mapper;

    /**
     * 數據庫加 version 版本號
     *
     * 實現 數據庫樂觀鎖
     *
     * 實現高並發下庫存的並發控制機制
     *
     * 要保證事務一致性,要么都使用mybatis  要么都使用jpa
     * @param map
     * @param entity
     * @param threadNum
     * @return
     */
    @Override
    @Transactional
    public void updateStock(Map<Integer,String> map, GoodsStock entity, Integer threadNum) {

        String  uid = entity.getUid();
        Long buyNum = entity.getBuyNum();
        String msg = "";
        //判斷庫存是否足夠
        GoodsStock old = mapper.findByUid(uid);
        Long stock = old.getStock();
        System.out.println("線程"+threadNum+"---------->正在工作");
        if (stock >= buyNum){
            old.setBuyNum(buyNum);
            if (mapper.updateStock(old) > 0 ){
                msg = "庫存扣除成功,剩余庫存數量:";
            }else {
                msg = "庫存扣除失敗,剩余庫存數量:";
            }
            Long nowStock = mapper.findByUid(uid).getStock();
            msg +=nowStock;
        }else {
            msg = "庫存不足,剩余庫存數量:"+stock;
        }
        map.put(threadNum,msg);
    }
View Code

 

4.controller層代碼:

 /**
     * uid代表            同一時間 大家都來買這一件東西
     * threadCount代表    同時會有多少人在操作
     * buyNum代表         同一個人的一次購買量
     * @param entity
     * @return
     */
    @RequestMapping(value = "/concurrentStock",method = RequestMethod.POST)
    public UniVerResponse<Map<Integer,String>> concurrentStock(@RequestBody GoodsStock entity){
        UniVerResponse.checkField(entity,"uid","threadCount","buyNum");
        UniVerResponse<Map<Integer,String>> res = new UniVerResponse<>();

        String uid = entity.getUid();

        GoodsStock old = service.findByUid(uid);
        if (old != null){
            //設置一個線程安全的Map記錄各個線程是否成功執行
            Map<Integer,String> map = new ConcurrentHashMap<Integer, String>();


            Integer threadCount = entity.getThreadCount();
            //所有線程阻塞,然后統一開始
            CountDownLatch begin = new CountDownLatch(1);

            //主線程阻塞,直到所有分線程執行完畢
            CountDownLatch end = new CountDownLatch(threadCount);

            //開始多線程
            begin.countDown();
            for (Integer i = 0; i < threadCount; i++) {
                Runnable runnable = buyGoods(map,entity,i,begin,end);
                new Thread(runnable).start();
            }

            //多個線程都執行結束
            try {
                end.await();
                res.beTrue(map);
            } catch (InterruptedException e) {
                e.printStackTrace();
                res.beFalse("多線程執行失敗",UniVerResponse.ERROR_BUSINESS,null);
            }
        }else {
            res.beFalse("商品不存在",UniVerResponse.ERROR_BUSINESS,null);
        }
        return  res;
    }


    //多線程的方法
    public Runnable buyGoods(Map<Integer,String> map, GoodsStock entity, Integer threadNum,CountDownLatch begin,CountDownLatch end){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                try {
                    System.out.println("線程"+threadNum+":--------------------->開始工作");
                    begin.await();

                    service.updateStock(map,entity,threadNum);

                    end.countDown();
                    System.out.println("線程"+threadNum+":--------------------->結束工作");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        return runnable;
    }
View Code

 

5.發送請求

 

第一次請求:

下圖所示,僅有線程編號為3的 線程  購買成功,其他都購買失敗。

 

 

第二次請求:

 

 

第三次請求:

 

 

第四次請求:

 

 

最后一次請求:

 

 

二.再說說在同一個事務中使用多個樂觀鎖的情況

===============================================================================================

下面僅寫一段代碼舉個例子即可:

 

即 第一步操作,第二步 都會使用樂觀鎖

如果執行失敗有兩種情況:

  1.數據庫連接斷開,sql真正的執行出錯

  2.sql成功執行,但是其實update執行失敗,因為version對應不起來

 

所以需要注意的是 如果使用樂觀鎖執行失敗[失敗情況2],那么需要自己手動去拋出異常,去保證事務的一致性!!!

因為失敗情況1自己會拋出RuntimeException

 

因為下面示例代碼中的第一步操作如果失敗了會直接返回  所以並沒有去拋異常

 /**
     * 進行兌換
     *
     * 1.減少會員積分總數[加樂觀鎖]
     *
     * 2.減少商品庫存 增加商品銷量[加樂觀鎖]
     *
     * 3.新增兌換記錄
     *
     *
     * @param entity
     * @return
     */
    @Override
    @Transactional
    public boolean insert(ExchangeOrder entity,String integralUid,Integer buyIntegral) {

        boolean isSuccess = false;

        //1.減少會員積分
        IntegralDetail integralDetail = integralDetailMapper.findByIntegralId(integralUid);
        integralDetail.setIntegralValue(buyIntegral);//sql 做減操作
        isSuccess = (integralDetailMapper.deductIntegral(integralDetail) > 0);

        if (isSuccess){
            //2.減少商品庫存  增加商品銷量
            IntegralGoods integralGoods = integralGoodsMapper.findByUid(entity.getIntegralGoodsId());
            //無限庫存不做修改
            if (integralGoods.getStock() != -1) {
                integralGoods.setStock(entity.getBuyNum());
            }
            //增加銷量
            integralGoods.setSaleNum(entity.getBuyNum());
            integralGoods.initUpdateDataMen();
            isSuccess = (integralGoodsMapper.updateStock(integralGoods) > 0);

            if (isSuccess){
                //3.新增兌換記錄
                mapper.insert(entity);
            }else{
                throw new RunException("銷量增加失敗,請稍后再試");
            }
        }

        return isSuccess;
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM