Java之流水号生成器实现

发布时间 2023-08-01 18:28:40作者: sunny123456

Java之流水号生成器实现
原文链接:https://www.jianshu.com/p/331b872e9c8f

开心一笑

提出问题

如何使用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);

           <span class="token comment">//动态日期的生成</span>
           <span class="token keyword">if</span><span class="token punctuation">(</span>pattern<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span><span class="token class-name">SerialNumConstants</span><span class="token punctuation">.</span>DATE_SYMBOL<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
               <span class="token class-name">String</span> currentDate <span class="token operator">=</span> <span class="token class-name">DateUtils</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token string">"yyyyMMdd"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
               formatSerialNum <span class="token operator">=</span> formatSerialNum<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token class-name">SerialNumConstants</span><span class="token punctuation">.</span>DATE_SYMBOL<span class="token punctuation">,</span>currentDate<span class="token punctuation">)</span><span class="token punctuation">;</span>
           <span class="token punctuation">}</span>
      
           resultList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>formatSerialNum<span class="token punctuation">)</span><span class="token punctuation">;</span>
       <span class="token punctuation">}</span>
       <span class="token comment">//更新数据</span>
       systemSerialNumberDTO<span class="token punctuation">.</span><span class="token function">setMaxSerial</span><span class="token punctuation">(</span>maxSerialInt <span class="token operator">+</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
       <span class="token class-name">SystemSerialNumber</span> systemSerialNumber <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SystemSerialNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
       <span class="token class-name">BeanUtils</span><span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>systemSerialNumber<span class="token punctuation">,</span>systemSerialNumberDTO<span class="token punctuation">)</span><span class="token punctuation">;</span>
       serialNumberRepository<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>systemSerialNumber<span class="token punctuation">)</span><span class="token punctuation">;</span>
      

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

}

读书感悟

  • 生活坏到一定程度就会好起来,因为它无法更坏。努力过后,才知道许多事情,坚持坚持,就过来了。
  • 有些烦恼,丢掉了,才有云淡风轻的机会。
  • 当一个胖纸没有什么不好,最起码可以温暖其他的人。