申明:早年的测试代码,未在生产环境使用。仅供参考
生成规则:
前缀
+递增序列
+后缀
(如:A000001B;ALI000001BABA)
数据库实体对象Sequence(注:mod 属性不在表中,这里用于取模,使value的值循环在指定长度中)
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
/**
* mst_sequence
* @author lix
* @Date 2020/4/24
*/
@TableName("mst_sequence")
public class Sequence {
/**
* 主键,类型
*/
@TableId
private String type;
/**
* 前缀
*/
private String prefix;
/**
* 后缀
*/
private String suffix;
/**
* 缓存大小
*/
@TableField("cache_size")
private int cacheSize;
/**
* 当前值
*/
private long value;
/**
* 本次缓存的临界值
*/
private long crucial;
/**
* value最大长度(不含前后缀)
*/
@TableField("value_length")
private int valueLen;
/**
* 版本号
*/
private long version;
/**
* 模
*/
@TableField(exist = false)
private int mod = 1;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getPrefix() {
return prefix == null ? "" : prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix == null ? "" : suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public int getCacheSize() {
return cacheSize;
}
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public long getCrucial() {
return crucial;
}
public void setCrucial(long crucial) {
this.crucial = crucial;
}
public int getValueLen() {
return valueLen;
}
public void setValueLen(int valueLen) {
this.valueLen = valueLen;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getMod() {
if (mod == 1) {
for (int i = 0; i < valueLen; i++) {
mod *= 10;
}
}
return mod;
}
/**
* 获取String补零format
* @return
*/
public String getValueFillFormat(){
return "%0" + this.valueLen + "d";
}
}
生成逻辑service(关于缓存序列号的问题这里不作处理,有nosql或者MQ的可以使用更好的方式处理,这里也不使用多线程异步更新,个人感觉意义不是很大。对于代码中的cacheMap各位码友根据自己情况优化)
import com.platform.mst.model.entity.Sequence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class SequenceService {
private final Map<String, Sequence> cacheMap = new ConcurrentHashMap<>();
@Autowired
private SequenceHelper helper;
public String serial(String type) {
synchronized (cacheMap.computeIfAbsent(type, k -> new Sequence())) { // 使用 computeIfAbsent 减少锁的范围
Sequence sequence = cacheMap.get(type);
if (sequence == null || sequence.getValue() >= sequence.getCrucial()) {
sequence = helper.getSequence(type);
cacheMap.put(type, sequence);
}
long nextVal = sequence.getValue() + 1;
sequence.setValue(nextVal);
long seq = nextVal % sequence.getMod();
return sequence.getPrefix() + String.format(sequence.getValueFillFormat(), seq) + sequence.getSuffix();
}
}
}
辅助类SequenceHelper(注:这里需要分开写在不同的类里,这里的代码不要和service放在一起,这里需要加入事物注解,创建新事物管理,无论service是否异常,这里必须提交成功,否则会出现重复序列)
import com.platform.mst.database.mapper.SequenceMapper;
import com.platform.mst.model.entity.Sequence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class SequenceHelper {
@Autowired
private SequenceMapper sequenceMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Sequence getSequence(String type) {
Sequence sequence;
int count;
do {
sequence = sequenceMapper.selectById(type);
if (sequence == null) throw new IllegalArgumentException(String.format("序列表中没有type为%s的数据", type));
sequence.setValue(sequence.getCrucial());
sequence.setCrucial(sequence.getCrucial() + sequence.getCacheSize());
count = sequenceMapper.updateByIdAndVersion(sequence); // 使用带有版本号的更新方法
} while (count < 1);
return sequence;
}
}
Mapper
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
@Mapper
public interface SequenceMapper {
@Select("SELECT * FROM mst_sequence WHERE type = #{type}")
Sequence selectById(String type);
@Update("UPDATE mst_sequence SET value = #{value}, crucial = #{crucial}, version = #{version} + 1 WHERE type = #{type} AND version = #{version}")
int updateByIdAndVersion(Sequence sequence); // 使用带有版本号的更新方法
}
mysql建表语句
CREATE TABLE `mst_sequence` (
`type` varchar(64) NOT NULL COMMENT '唯一类型',
`prefix` varchar(4) DEFAULT NULL COMMENT '前缀',
`suffix` varchar(4) DEFAULT NULL COMMENT '后缀',
`cache_size` smallint(3) NOT NULL DEFAULT '0' COMMENT '缓存大小',
`value` bigint(16) NOT NULL DEFAULT '0' COMMENT '当前值',
`crucial` bigint(16) NOT NULL DEFAULT '0' COMMENT '本次缓存的临界值',
`value_length` tinyint(2) NOT NULL COMMENT '值得长度(不含前后缀)',
`version` bigint(16) NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;