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

}

读书感悟

  • 生活坏到一定程度就会好起来,因为它无法更坏。努力过后,才知道许多事情,坚持坚持,就过来了。
  • 有些烦恼,丢掉了,才有云淡风轻的机会。
  • 当一个胖纸没有什么不好,最起码可以温暖其他的人。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,064评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,946评论 4 60
  • 作业1:好好回想一下,小时候呆呆看过什么。挑选一个印象或一幅画面写出来。 小时候常常会坐在窗户边往外看风景。老家的...
    王姝娆阅读 197评论 4 0
  • ERROR: Error during SonarQube Scanner execution ERROR: Fa...
    azhao阅读 7,052评论 0 1