Java之流水號生成器實現(xiàn)

開心一笑

搞笑.png

提出問題

如何使用jAVA生成流水號梭姓,同時支持可配置和高并發(fā)???

解決問題

假設(shè)你們項目已經(jīng)整合緩存技術(shù)
假如你有一定的Java基礎(chǔ)
假如......

下面的代碼實現(xiàn)的是一個支持高并發(fā),可配置嘉熊,效率高的流水號生成器涂召,
可同時為一個項目的多個模塊使用供炼,流水號支持緩存昔头,即每次會預(yù)先生成一定數(shù)量的流水號存放在緩存中洛口,
需要的時候,優(yōu)先到緩存中去蹂空,緩存中的序列號使用完之后俯萌,重新生成一定數(shù)量的流水號放到緩存中,如此循環(huán)上枕,提高效率......
同時咐熙,該流水號生成器是線程安全的,使用線程鎖進(jìn)行保護(hù),已經(jīng)真正的投入到項目中使用......

數(shù)據(jù)庫表設(shè)計

CREATE TABLE sys_serial_number2 (
    "id" varchar(32) COLLATE "default" NOT NULL,
    "module_name" varchar(50) COLLATE "default",
    "module_code" varchar(50) COLLATE "default",
    "config_templet" varchar(50) COLLATE "default",
    "max_serial" varchar(32) COLLATE "default",
    "pre_max_num" varchar(32) COLLATE "default",
    "is_auto_increment" char(1) COLLATE "default"
)

說明:

module_name:模塊名稱
module_code:模塊編碼
config_templet:當(dāng)前模塊 使用的序列號模板
max_serial:存放當(dāng)前序列號的值
pre_max_num:預(yù)生成序列號存放到緩存的個數(shù)
is_auto_increment:是否自動增長模式辨萍,0:否  1:是

注意:目前序列號模板只支持字母棋恼,動態(tài)數(shù)字(0000 代表1-9999),和日期用${DATE}的組合形式
is_auto_increment配置為1 锈玉,這時配置模板為CX000000生成的序列號為:CX1 ,CX2爪飘,CX3.....
配置為0,這時配置模板為CX0000000生成的序列號為:CX00000001,CX00000002拉背,CX00000003

數(shù)據(jù)庫配置說明:如需要項目模塊的項目編號师崎,則需要在數(shù)據(jù)庫表sys_serial_number中配置一條記錄:

|  id   |  module_name |  module_code |  config_templet | max_serial  | pre_max_num |  is_auto_increment
|-------|--------------|--------------|-----------------|-------------|-------------|--------------------/
|  xxxx |  項目         |  PJ         |CX00000000${DATE}|  2650       |  100        |    1

CX00000000${DATE}生成的序列號類似于:CX0000000120160522 ,CX0000000220160522椅棺,CX0000000320160522 ......

序列號model實體設(shè)計:

package com.evada.de.serialnum.model;


import com.evada.de.common.model.BaseModel;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

/**
 * 功能描述:序列號表模型
 *
 * @author :Ay 2015/11/23
 */
@Entity
@Table(name="sys_serial_number")
public class SystemSerialNumber extends BaseModel {

    /**
     * 模塊名稱
     */
    @Column(name = "module_name", columnDefinition = "VARCHAR")
    private String moduleName;

    /**
     * 模塊編碼
     */
    @Column(name = "module_code", columnDefinition = "VARCHAR")
    private String moduleCode;

    /**
     * 流水號配置模板
     */
    @Column(name = "config_templet", columnDefinition = "VARCHAR")
    private String configTemplet;

    /**
     * 序列號最大值
     */
    @Column(name = "max_serial", columnDefinition = "VARCHAR")
    private String maxSerial;

    /**
     * 是否自動增長標(biāo)示
     */
    @Column(name = "is_auto_increment", columnDefinition = "VARCHAR")
    private String isAutoIncrement;

    public String getIsAutoIncrement() {
        return isAutoIncrement;
    }

    public void setIsAutoIncrement(String isAutoIncrement) {
        this.isAutoIncrement = isAutoIncrement;
    }

    /**
     * 預(yù)生成流水號數(shù)量
     */
    @Column(name = "pre_max_num", columnDefinition = "VARCHAR")
    private String preMaxNum;

    public String getPreMaxNum() {
        return preMaxNum;
    }

    public void setPreMaxNum(String preMaxNum) {
        this.preMaxNum = preMaxNum;
    }

    public String getModuleName() {
        return moduleName;
    }

    public void setModuleName(String moduleName) {
        this.moduleName = moduleName;
    }

    public String getModuleCode() {
        return moduleCode;
    }

    public void setModuleCode(String moduleCode) {
        this.moduleCode = moduleCode;
    }

    public String getConfigTemplet() {
        return configTemplet;
    }

    public void setConfigTemplet(String configTemplet) {
        this.configTemplet = configTemplet;
    }

    public String getMaxSerial() {
        return maxSerial;
    }

    public void setMaxSerial(String maxSerial) {
        this.maxSerial = maxSerial;
    }

    public SystemSerialNumber(String id){
        this.id = id;
    }

    public  SystemSerialNumber(String id,String moduleCode){
        this.id = id;
        this.moduleCode = moduleCode;
    }

    public SystemSerialNumber(){}
}

Service接口設(shè)計:

package com.evada.de.serialnum.service;

import com.evada.de.serialnum.dto.SystemSerialNumberDTO;

/**
 * 序列號service接口
 * Created by huangwy on 2015/11/24.
 */
public interface ISerialNumService {

    public SystemSerialNumberDTO find(SystemSerialNumberDTO systemSerialNumberDTO);
        
    public String generateSerialNumberByModelCode(String moduleCode);

    /**
     * 設(shè)置最小值
     * @param value 最小值犁罩,要求:大于等于零
     * @return      流水號生成器實例
     */
    ISerialNumService setMin(int value);

    /**
     * 設(shè)置最大值
     * @param value 最大值齐蔽,要求:小于等于Long.MAX_VALUE ( 9223372036854775807 )
     * @return      流水號生成器實例
     */
    ISerialNumService setMax(long value);

    /**
     * 設(shè)置預(yù)生成流水號數(shù)量
     * @param count 預(yù)生成數(shù)量
     * @return      流水號生成器實例
     */
    ISerialNumService setPrepare(int count);
}

Service實現(xiàn):

package com.evada.de.serialnum.service.impl;

import com.evada.de.common.constants.SerialNumConstants;
import com.evada.de.serialnum.dto.SystemSerialNumberDTO;
import com.evada.de.serialnum.model.SystemSerialNumber;
import com.evada.de.serialnum.repository.SerialNumberRepository;
import com.evada.de.serialnum.repository.mybatis.SerialNumberDAO;
import com.evada.de.serialnum.service.ISerialNumService;
import com.evada.inno.common.util.BeanUtils;
import com.evada.inno.common.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by Ay on 2015/11/24.
 */
@Service("serialNumberService")
public class SerialNumberServiceImpl implements ISerialNumService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SerialNumberServiceImpl.class);

    @Autowired
    private SerialNumberDAO serialNumberDAO;

    @Autowired
    private SerialNumberRepository serialNumberRepository;

    /** 格式 */
    private String pattern = "";

    /** 生成器鎖 */
    private final ReentrantLock lock = new ReentrantLock();

    /** 流水號格式化器 */
    private DecimalFormat format = null;

    /** 預(yù)生成鎖 */
    private final ReentrantLock prepareLock = new ReentrantLock();

    /** 最小值 */
    private int min = 0;

    /** 最大值 */
    private long max = 0;

    /** 已生成流水號(種子) */
    private long seed = min;

    /** 預(yù)生成數(shù)量 */
    private int prepare = 0;

    /** 數(shù)據(jù)庫存儲的當(dāng)前最大序列號 **/
    long maxSerialInt = 0;

    /** 當(dāng)前序列號是否為個位數(shù)自增的模式 **/
    private String isAutoIncrement = "0";

    SystemSerialNumberDTO systemSerialNumberDTO =  new SystemSerialNumberDTO();

    /** 預(yù)生成流水號 */
    HashMap<String,List<String>> prepareSerialNumberMap = new HashMap<>();

    /**
     * 查詢單條序列號配置信息
     * @param systemSerialNumberDTO
     * @return
     */
    @Override
    public SystemSerialNumberDTO find(SystemSerialNumberDTO systemSerialNumberDTO) {
        return serialNumberDAO.find(systemSerialNumberDTO);
    }

    /**
     * 根據(jù)模塊code生成預(yù)數(shù)量的序列號存放到Map中
     * @param moduleCode 模塊code
     * @return
     */
    @CachePut(value = "serialNumber",key="#moduleCode")
    public List<String> generatePrepareSerialNumbers(String moduleCode){
        //臨時List變量
        List<String> resultList = new ArrayList<String>(prepare);
        lock.lock();
        try{
            for(int i=0;i<prepare;i++){
                maxSerialInt  = maxSerialInt + 1;
                if(maxSerialInt > min && (maxSerialInt + "").length() < max ){
                    seed = maxSerialInt ;
                }else{
                    //如果動態(tài)數(shù)字長度大于模板中的長度 例:模板CF000  maxSerialInt 1000
                    seed = maxSerialInt = 0;
                    //更新數(shù)據(jù),重置maxSerialInt為0
                    systemSerialNumberDTO.setMaxSerial("0");
                    SystemSerialNumber systemSerialNumber = new SystemSerialNumber();
                    BeanUtils.copyProperties(systemSerialNumber,systemSerialNumberDTO);
                    serialNumberRepository.save(systemSerialNumber);
                }
                 //動態(tài)數(shù)字生成
                 String formatSerialNum = format.format(seed);

                //動態(tài)日期的生成
                if(pattern.contains(SerialNumConstants.DATE_SYMBOL)){
                    String currentDate = DateUtils.format(new Date(),"yyyyMMdd");
                    formatSerialNum = formatSerialNum.replace(SerialNumConstants.DATE_SYMBOL,currentDate);
                }

                resultList.add(formatSerialNum);
            }
            //更新數(shù)據(jù)
            systemSerialNumberDTO.setMaxSerial(maxSerialInt + "");
            SystemSerialNumber systemSerialNumber = new SystemSerialNumber();
            BeanUtils.copyProperties(systemSerialNumber,systemSerialNumberDTO);
            serialNumberRepository.save(systemSerialNumber);
        }finally{
            lock.unlock();
        }
        return resultList;
    }

    /**
     * 根據(jù)模塊code生成序列號
     * @param moduleCode  模塊code
     * @return  序列號
     */
    public String generateSerialNumberByModelCode(String moduleCode){

        //預(yù)序列號加鎖
        prepareLock.lock();
        try{
            //判斷內(nèi)存中是否還有序列號
            if(null != prepareSerialNumberMap.get(moduleCode) && prepareSerialNumberMap.get(moduleCode).size() > 0){
                //若有昼汗,返回第一個肴熏,并刪除
                return prepareSerialNumberMap.get(moduleCode).remove(0);
            }
        }finally {
            //預(yù)序列號解鎖
            prepareLock.unlock();
        }
        systemSerialNumberDTO = new SystemSerialNumberDTO();
        systemSerialNumberDTO.setModuleCode(moduleCode);
        systemSerialNumberDTO = serialNumberDAO.find(systemSerialNumberDTO);
        prepare = Integer.parseInt(systemSerialNumberDTO.getPreMaxNum().trim());//預(yù)生成流水號數(shù)量
        pattern = systemSerialNumberDTO.getConfigTemplet().trim();//配置模板
        String maxSerial = systemSerialNumberDTO.getMaxSerial().trim(); //存儲當(dāng)前最大值
        isAutoIncrement = systemSerialNumberDTO.getIsAutoIncrement().trim();
        maxSerialInt = Long.parseLong(maxSerial.trim());//數(shù)據(jù)庫存儲的最大序列號
        max = this.counter(pattern,'0') + 1;//根據(jù)模板判斷當(dāng)前序列號數(shù)字的最大值
        if(isAutoIncrement.equals("1")){
            pattern = pattern.replace("0","#");
        }
        format = new DecimalFormat(pattern);
        //生成預(yù)序列號,存到緩存中
        List<String> resultList = generatePrepareSerialNumbers(moduleCode);
        prepareLock.lock();
        try {
            prepareSerialNumberMap.put(moduleCode, resultList);
            return prepareSerialNumberMap.get(moduleCode).remove(0);
        } finally {
            prepareLock.unlock();
        }
    }

    /**
     * 設(shè)置最小值
     *
     * @param value 最小值顷窒,要求:大于等于零
     * @return 流水號生成器實例
     */
    public ISerialNumService setMin(int value) {
        lock.lock();
        try {
            this.min = value;
        }finally {
            lock.unlock();
        }
        return this;
    }

    /**
     * 最大值
     *
     * @param value 最大值蛙吏,要求:小于等于Long.MAX_VALUE ( 9223372036854775807 )
     * @return 流水號生成器實例
     */
    public ISerialNumService setMax(long value) {
        lock.lock();
        try {
            this.max = value;
        }finally {
            lock.unlock();
        }
        return this;
    }

    /**
     * 設(shè)置預(yù)生成流水號數(shù)量
     * @param count 預(yù)生成數(shù)量
     * @return      流水號生成器實例
     */
    public ISerialNumService setPrepare(int count) {
        lock.lock();
        try {
            this.prepare = count;
        }finally {
            lock.unlock();
        }
        return this;
    }

    /**
     * 統(tǒng)計某一個字符出現(xiàn)的次數(shù)
     * @param str 查找的字符
     * @param c
     * @return
     */
    private int counter(String str,char c){
        int count=0;
        for(int i = 0;i < str.length();i++){
            if(str.charAt(i)==c){
                count++;
            }
        }
        return count;
    }

}

讀書感悟

  • 生活壞到一定程度就會好起來,因為它無法更壞鞋吉。努力過后鸦做,才知道許多事情,堅持堅持谓着,就過來了泼诱。
  • 有些煩惱,丟掉了赊锚,才有云淡風(fēng)輕的機(jī)會治筒。
  • 當(dāng)一個胖紙沒有什么不好,最起碼可以溫暖其他的人舷蒲。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耸袜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牲平,更是在濱河造成了極大的恐慌堤框,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纵柿,死亡現(xiàn)場離奇詭異蜈抓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)昂儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門沟使,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渊跋,你說我怎么就攤上這事格带。” “怎么了刹枉?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵叽唱,是天一觀的道長。 經(jīng)常有香客問我微宝,道長棺亭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任蟋软,我火速辦了婚禮镶摘,結(jié)果婚禮上嗽桩,老公的妹妹穿的比我還像新娘。我一直安慰自己凄敢,他們只是感情好碌冶,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝缝,像睡著了一般扑庞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拒逮,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天罐氨,我揣著相機(jī)與錄音,去河邊找鬼滩援。 笑死栅隐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玩徊。 我是一名探鬼主播租悄,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恩袱!你這毒婦竟也來了泣棋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤憎蛤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纪吮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俩檬,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年碾盟,在試婚紗的時候發(fā)現(xiàn)自己被綠了棚辽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡冰肴,死狀恐怖屈藐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熙尉,我是刑警寧澤联逻,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站检痰,受9級特大地震影響包归,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铅歼,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一公壤、第九天 我趴在偏房一處隱蔽的房頂上張望换可。 院中可真熱鬧,春花似錦厦幅、人聲如沸沾鳄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽译荞。三九已至,卻和暖如春缚态,著一層夾襖步出監(jiān)牢的瞬間磁椒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工玫芦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留浆熔,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓桥帆,卻偏偏與公主長得像医增,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子老虫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理叶骨,服務(wù)發(fā)現(xiàn),斷路器祈匙,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,757評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫忽刽、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • 作業(yè)1:好好回想一下夺欲,小時候呆呆看過什么跪帝。挑選一個印象或一幅畫面寫出來。 小時候常常會坐在窗戶邊往外看風(fēng)景些阅。老家的...
    王姝嬈閱讀 204評論 4 0
  • ERROR: Error during SonarQube Scanner execution ERROR: Fa...
    azhao閱讀 7,082評論 0 1