Java之流水號生成器實現

開心一笑

搞笑.png

提出問題

如何使用jAVA生成流水號,同時支持可配置和高并發???

解決問題

假設你們項目已經整合緩存技術
假如你有一定的Java基礎
假如......

下面的代碼實現的是一個支持高并發,可配置,效率高的流水號生成器,
可同時為一個項目的多個模塊使用,流水號支持緩存,即每次會預先生成一定數量的流水號存放在緩存中,
需要的時候,優先到緩存中去,緩存中的序列號使用完之后,重新生成一定數量的流水號放到緩存中,如此循環,提高效率......
同時,該流水號生成器是線程安全的,使用線程鎖進行保護,已經真正的投入到項目中使用......

數據庫表設計

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:當前模塊 使用的序列號模板
max_serial:存放當前序列號的值
pre_max_num:預生成序列號存放到緩存的個數
is_auto_increment:是否自動增長模式,0:否  1:是

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

數據庫配置說明:如需要項目模塊的項目編號,則需要在數據庫表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實體設計:

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;

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

    public String getIsAutoIncrement() {
        return isAutoIncrement;
    }

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

    /**
     * 預生成流水號數量
     */
    @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接口設計:

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);

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

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

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

Service實現:

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;

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

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

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

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

    /** 預生成數量 */
    private int prepare = 0;

    /** 數據庫存儲的當前最大序列號 **/
    long maxSerialInt = 0;

    /** 當前序列號是否為個位數自增的模式 **/
    private String isAutoIncrement = "0";

    SystemSerialNumberDTO systemSerialNumberDTO =  new SystemSerialNumberDTO();

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

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

    /**
     * 根據模塊code生成預數量的序列號存放到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{
                    //如果動態數字長度大于模板中的長度 例:模板CF000  maxSerialInt 1000
                    seed = maxSerialInt = 0;
                    //更新數據,重置maxSerialInt為0
                    systemSerialNumberDTO.setMaxSerial("0");
                    SystemSerialNumber systemSerialNumber = new SystemSerialNumber();
                    BeanUtils.copyProperties(systemSerialNumber,systemSerialNumberDTO);
                    serialNumberRepository.save(systemSerialNumber);
                }
                 //動態數字生成
                 String formatSerialNum = format.format(seed);

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

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

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

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

    /**
     * 設置最小值
     *
     * @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;
    }

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

    /**
     * 統計某一個字符出現的次數
     * @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;
    }

}

讀書感悟

  • 生活壞到一定程度就會好起來,因為它無法更壞。努力過后,才知道許多事情,堅持堅持,就過來了。
  • 有些煩惱,丟掉了,才有云淡風輕的機會。
  • 當一個胖紙沒有什么不好,最起碼可以溫暖其他的人。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,948評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,349評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,245評論 4 61
  • 作業1:好好回想一下,小時候呆呆看過什么。挑選一個印象或一幅畫面寫出來。 小時候常常會坐在窗戶邊往外看風景。老家的...
    王姝嬈閱讀 222評論 4 0
  • ERROR: Error during SonarQube Scanner execution ERROR: Fa...
    azhao閱讀 7,114評論 0 1