开心一笑
提出问题
如何使用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;
}
}
读书感悟
- 生活坏到一定程度就会好起来,因为它无法更坏。努力过后,才知道许多事情,坚持坚持,就过来了。
- 有些烦恼,丢掉了,才有云淡风轻的机会。
- 当一个胖纸没有什么不好,最起码可以温暖其他的人。