Java之流水号生成器实现

作者: 阿_毅 | 来源:发表于2016-06-26 15:13 被阅读3338次

    开心一笑

    搞笑.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;
        }
    
    }
    

    读书感悟

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

    相关文章

      网友评论

      • ccf6d82047a3:流水号缓存具体是怎么实现的?缓存类SerialNumberRepository是怎么实现的?
      • 3b75c497f6e0:有个小问题,就是如果将流水号取到内存后,重新部署或者服务器宕机,那么在内存中的流水号是不是就丢失了? 这个问题po主是怎么解决呢?
        3b75c497f6e0:@阿_毅 我看代码,咱是预生成序列号的时候就把数据库中的序列号的最大值修改了,如果宕机的话,下次再从数据库中取最大值的话,内存中存储的那些预生成还没有用到的序列号就会丢掉了,不过如果从内存取序列号的时候再更新数据库话,多台应用服务器的话预生成的序列号就会重复,没想到特别好的方案
        阿_毅:@水手灬达 宕机的话,序列号就没掉了,但是数据库有存当前生成的最大序列号是哪个,接下来的序列号会在最大序列号下继续生成
      • 3b75c497f6e0:感谢分享 正好满足需求 学习一下
      • 2ca8523e79f3::smile:
        阿_毅: @瞎看什么 😃
      • 圆梦人生: :smile:
        阿_毅: @圆梦人生 😃

      本文标题:Java之流水号生成器实现

      本文链接:https://www.haomeiwen.com/subject/uooadttx.html