MyBatis中調用Mysql存儲過程實現(xiàn)序列碼自增

背景

項目中使用mysql作為數(shù)據(jù)庫页畦,針對項目中各種需要自增返回序列碼值的場景(批次ID數(shù)據(jù)、自定義規(guī)則的序列碼ID等)需要提供一個序列碼池表進行維護煌妈。

實際使用中我們使用mysql的存儲過程進行實現(xiàn)夯接,同時示例如何使用mybatis進行調用冯勉。

通用序列碼池表(common_sequence)

-- ----------------------------
-- Table structure for common_sequence
-- ----------------------------
DROP TABLE IF EXISTS `common_sequence`;
CREATE TABLE `common_sequence` (
  `key` varchar(40) NOT NULL COMMENT '序列key',
  `value` bigint NOT NULL DEFAULT '0' COMMENT '序列value',
  `remark` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '備注說明',
  `step_value` int NOT NULL DEFAULT '1' COMMENT '步長值(每次累加值)',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  `reset_rule` enum('none','month','day') CHARACTER SET utf8mb4 NOT NULL DEFAULT 'none' COMMENT '重置序列值,規(guī)則(none:不重置,month:按月重置,day:按天重置)',
  PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用序列碼池表';

字段特殊說明
step_value:每次自增的步長值,一般為1
reset_rule:有些需求要求按照月或日重置value值為初始值,例如:每個月或每天從001開始,則設置該字段

示例

image.png

存儲過程(next_seq_value)

-- -------------------------------------------------------------------
-- seq_key      入?yún)? 對應序列碼入?yún)嶓w中的seqKey      查詢key
-- seq_value    出參  對應序列碼入?yún)嶓w中的seqValue    返回value
-- -------------------------------------------------------------------
CREATE PROCEDURE `next_seq_value`(IN seq_key varchar(255), OUT seq_value bigint(20))
BEGIN
  -- 定義序列值
  DECLARE temp_value BIGINT default 0;
  -- 獲取當前值、當前步長亦镶、重置規(guī)則日月、更新時間變量
  SELECT value,step_value,reset_rule,update_time INTO @current_value,@current_step_value,@reset_rule,@update_time FROM COMMON_SEQUENCE t WHERE t.key =seq_key;
  CASE
    WHEN @reset_rule ='month' THEN
      -- 判斷當前日期與更新日期是否同一個月
      IF DATE_FORMAT(@update_time,'%Y%m')=DATE_FORMAT(now(),'%Y%m') THEN
        -- 如果是同一個月,序列值:[當前序列值+當前步長]
        SET temp_value=@current_value + @current_step_value;
      ELSE
        -- 如果不是同一個月,序列值:[序列值設置為0+當前步長]
        SET temp_value=temp_value + @current_step_value;
      END IF;
    WHEN @reset_rule ='day' THEN
      -- 判斷當前日期與更新日期是否同一天
      IF DATE_FORMAT(@update_time,'%Y%m%d')=DATE_FORMAT(now(),'%Y%m%d') THEN
        -- 如果是同一個天,序列值:[當前序列值+當前步長]
        SET temp_value=@current_value + @current_step_value;
      ELSE
        -- 如果不是同一個天,序列值:[序列值設置為0+當前步長]
        SET temp_value=temp_value + @current_step_value;
      END IF;
    ELSE
    -- 默認'none',序列值:[當前序列值+當前步長]
    SET temp_value=@current_value + @current_step_value;
  END CASE;
  -- 更新序列表
  UPDATE COMMON_SEQUENCE t1
  SET t1.value = temp_value
  WHERE t1.key = seq_key AND @current_value = t1.value;
  -- 將序列值返回
  SELECT temp_value INTO seq_value;
END;

封裝存儲過程中入?yún)⑴c出參的Java實體類

import lombok.Data;

/**
 * 獲取序列參數(shù)對象
 *
 * @author sdevil507
 * created on 2021/4/6
 */
@Data
public class CommonSequenceParams {

    /**
     * [IN]序列碼的key
     */
    private String seqKey;

    /**
     * [OUT]序列碼的值
     */
    private long seqValue;

}

mybatis dao示例

import org.apache.ibatis.annotations.Mapper;

/**
 * 序列碼池Dao
 *
 * @author sdevil507
 * created on 2021/4/6
 */
@Mapper
public interface CommonSequenceDao {

    /**
     * 獲取下一序列值
     *
     * @param params 入?yún)?[IN:string]key=seqName,[OUT:long]key=seqNo)
     */
    void nextSeqValue(CommonSequenceParams params);
}

mybatis 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.yama.eraims.equities.helper.seq.CommonSequenceDao">

    <!--suppress SqlNoDataSourceInspection -->
    <!--注意此處 parameterType 參數(shù)是一個封裝入?yún)⑴c出參的類 -->
    <select id="nextSeqValue" parameterType="com.xxx.xxx.CommonSequenceParams"
            statementType="CALLABLE">
        CALL next_seq_value(#{seqKey,mode=IN}, #{seqValue,mode=OUT,jdbcType=BIGINT})
    </select>

</mapper>

方法中具體調用示例

   /**
     * 生成卡批次ID序列號
     * <p>
     * 規(guī)則:[pc+5位數(shù)字(00001開始,順序遞增)]
     *
     * @return 卡批次ID
     */
    public String generatePcId() {
        // 創(chuàng)建封裝入?yún)⒊鰠嶓w類實例
        CommonSequenceParams params = new CommonSequenceParams();
        // 設置查詢key值
        params.setSeqKey("pc");
        // 執(zhí)行存儲過程key對應value值+1
        commonSequenceDao.nextSeqValue(params);
        // 執(zhí)行獲取返回的序列碼值
        return "pc" + String.format("%05d", params.getSeqValue());
    }

并發(fā)控制

直接調用存儲過程獲取序列號的過程是不安全的爱咬,如果存在多線程情況下會導致序列碼重復

解決辦法:使用時加鎖進行控制

1.提供全局共享對象鎖

/**
 * 序列碼鎖
 *
 * @author sdevil507
 * created on 2023/4/24
 */
public interface SequenceLocker {

    /**
     * 全局共享對象鎖
     */
    Object lock = new Object();
}

2.調用處進行加鎖控制(此處使用性能測試的代碼)

/**
 * 序列生成性能
 *
 * @author sdevil507
 * created on 2023/4/24
 */
public class SequencePerformanceRunner implements Runnable {

    // seq dao
    private final CommonSequenceDao commonSequenceDao;

    //每個線程的執(zhí)行次數(shù)
    private final int size;

    //記錄多線程的總執(zhí)行次數(shù),保證高并發(fā)下的原子性
    public static AtomicInteger atomicInteger = new AtomicInteger(0);

    public SequencePerformanceRunner(CommonSequenceDao commonSequenceDao, int size) {
        this.commonSequenceDao = commonSequenceDao;
        this.size = size;
    }

    @Override
    public void run() {
        int count = 0;
        while (count < size) {
            count++;
            atomicInteger.getAndIncrement();
            CommonSequenceParams params = new CommonSequenceParams();
            params.setSeqKey("test_seq");
            // 注意:此處加鎖進行控制,生成序列碼
            synchronized (SequenceLocker.lock) {
                commonSequenceDao.nextSeqValue(params);
            }
            System.out.println("線程ID與對應的執(zhí)行次數(shù):" + Thread.currentThread().getId() + "--->" + count);
        }
    }
}

3.測試

/**
 * 序列池測試
 *
 * @author sdevil507
 */
@SpringBootTest
public class CommonSequenceDaoTest {

    @Autowired
    private CommonSequenceDao commonSequenceDao;

    /**
     * 測試并發(fā)環(huán)境下序列碼生成性能
     */
    @Test
    public void testConcurrentSequencePerformance() throws InterruptedException {
        //開啟的線程數(shù)
        int threadSize = 150;
        //創(chuàng)建線程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadSize);
        //開始時間
        long start = System.currentTimeMillis();
        //讓線程池中的每一個線程都開始工作,最終生成目標150*150=22500序列碼數(shù)
        for (int i = 0; i < threadSize; i++) {
            //執(zhí)行線程
            executorService.execute(new SequencePerformanceRunner(commonSequenceDao, threadSize));
        }
        //等線程全部執(zhí)行完后關閉線程池
        executorService.shutdown();
        //noinspection ResultOfMethodCallIgnored
        executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
        //結束時間
        long end = System.currentTimeMillis();

        System.out.println("測試次數(shù):" + SequencePerformanceRunner.atomicInteger.get());
        System.out.println("用時(毫秒):" + (end - start));
        System.out.println("速度(次/秒):" + SequencePerformanceRunner.atomicInteger.get() * 1000L / (end - start));
    }
}

性能測試結果

上面性能測試代碼開啟150線程,每個線程執(zhí)行150次,總共生成150*150=22500個序列碼

結果如下:

測試次數(shù):22500
用時(毫秒):97346
速度(次/秒):231

每秒生成230個左右序列碼,目前對于我們的業(yè)務系統(tǒng)來說綽綽有余绊起!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末精拟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虱歪,更是在濱河造成了極大的恐慌蜂绎,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笋鄙,死亡現(xiàn)場離奇詭異师枣,居然都是意外死亡,警方通過查閱死者的電腦和手機萧落,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門践美,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洗贰,“玉大人,你說我怎么就攤上這事拨脉《咭觯” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵玫膀,是天一觀的道長矛缨。 經常有香客問我,道長帖旨,這世上最難降的妖魔是什么箕昭? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮解阅,結果婚禮上落竹,老公的妹妹穿的比我還像新娘。我一直安慰自己货抄,他們只是感情好述召,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蟹地,像睡著了一般积暖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怪与,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天夺刑,我揣著相機與錄音,去河邊找鬼分别。 笑死遍愿,一個胖子當著我的面吹牛,可吹牛的內容都是我干的耘斩。 我是一名探鬼主播沼填,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煌往!你這毒婦竟也來了倾哺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刽脖,失蹤者是張志新(化名)和其女友劉穎羞海,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曲管,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡却邓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了院水。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腊徙。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡简十,死狀恐怖,靈堂內的尸體忽然破棺而出撬腾,到底是詐尸還是另有隱情螟蝙,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布民傻,位于F島的核電站胰默,受9級特大地震影響,放射性物質發(fā)生泄漏漓踢。R本人自食惡果不足惜牵署,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喧半。 院中可真熱鬧奴迅,春花似錦、人聲如沸挺据。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扁耐。三九已至者填,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間做葵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工心墅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酿矢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓怎燥,卻偏偏與公主長得像瘫筐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铐姚,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容