diff --git a/README.md b/README.md
index 49d656b..a4c37da 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,404 @@
-# seq
+# seq——基于mysql+spring-jdbc的自增序号生成器
-基于mysql+spring-jdbc的自增序号生成器
\ No newline at end of file
+---
+
+用于生成全局自增序号,跳过的序号可以回收使用。
+
+---
+
+使用方法:
+
++ 在项目中放置jar包的地方把seq-1.0.0.jar、seq-1.0.0-sources.jar、seq-1.0.0-pom.xml复制过去
++ 在pom.xml中增加以下内容,然后执行maven命令:mvn clean
+
+```xml
+
+
+
+
+ com.cdhncy
+ seq
+ 1.0.0
+
+
+
+ org.springframework
+ spring-jdbc
+
+
+
+ mysql
+ mysql-connector-java
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+ 2.5
+
+
+ install-external
+ clean
+
+ install-file
+
+
+
+ ${project.basedir}/lib/seq-1.0.0.jar
+ ${pom.basedir}/lib/seq-1.0.0-pom.xml
+ ${project.basedir}/lib/seq-1.0.0-sources.jar
+ default
+ com.cdhncy
+ seq
+ 1.0.0
+ jar
+ true
+
+
+
+
+
+```
+
++ springboot中配置方式一(优先):直接注入已有jdbcTemplate和transactionTemplate
+
+```java
+package com.yang.springseq.config;
+
+import com.cdhncy.seq.config.GeneratorConfig;
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.generator.Generator;
+import com.cdhncy.seq.generator.impl.SequencesGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.annotation.Resource;
+
+/**
+ * 基于已有的jdbcTemplate和transactionTemplate,一般如果引用了spring的数据库操作,如jpa、mybatis,都可以直接注入
+ */
+@Configuration
+public class SeqGeneratorConfig {
+ /**
+ * 注入已有的数据库操作模板
+ */
+ @Resource
+ private JdbcTemplate jdbcTemplate;
+ /**
+ * 注入已有的事务操作模板
+ */
+ @Resource
+ private TransactionTemplate transactionTemplate;
+
+ /**
+ * 序号表配置类
+ */
+ @Bean
+ public TableConfig tableConfig() {
+ TableConfig tableConfig = new TableConfig();
+ //自定义表名、字段名
+ //tableConfig.setTable("sequences");
+ //tableConfig.setKeyColumn("SEQUENCE_KEY");
+ //tableConfig.setTypeColumn("SEQUENCE_TYPE");
+ //tableConfig.setSeqColumn("SEQUENCE_NEXT_ID");
+ //tableConfig.setCreateTimeColumn("CREATE_TIME");
+ return tableConfig;
+ }
+
+ /**
+ * 序号生成器配置类
+ * @param tableConfig 序号表配置类
+ */
+ @Bean
+ public GeneratorConfig generatorConfig(TableConfig tableConfig) {
+ GeneratorConfig generatorConfig = new GeneratorConfig();
+ generatorConfig.setJdbcTemplate(jdbcTemplate);
+ generatorConfig.setTransactionTemplate(transactionTemplate);
+ generatorConfig.setTableConfig(tableConfig);
+ return generatorConfig;
+ }
+
+ /**
+ * 注册序号生成器类
+ * @param generatorConfig 序号生成器配置类
+ */
+ @Bean
+ public Generator generator(GeneratorConfig generatorConfig) {
+ return new SequencesGenerator(generatorConfig);
+ }
+}
+```
+
++ springboot中配置方式二:注入已有的dataSource或自行构建dataSource,通过dataSource自动生成jdbcTemplate和transactionTemplate
+
+```java
+package com.yang.springseq.config;
+
+import com.cdhncy.seq.config.GeneratorConfig;
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.generator.Generator;
+import com.cdhncy.seq.generator.impl.SequencesGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+/**
+ * 注入已有的dataSource或自行构建dataSource,通过dataSource自动生成jdbcTemplate和transactionTemplate
+ */
+@Configuration
+public class SeqGeneratorConfig {
+ /**
+ * 注入已有的数据源,果没有,也可以自行构建
+ */
+ @Resource
+ private DataSource dataSource;
+
+ /**
+ * 序号表配置类
+ */
+ @Bean
+ public TableConfig tableConfig() {
+ TableConfig tableConfig = new TableConfig();
+ //自定义表名、字段名
+ //tableConfig.setTable("sequences");
+ //tableConfig.setKeyColumn("SEQUENCE_KEY");
+ //tableConfig.setTypeColumn("SEQUENCE_TYPE");
+ //tableConfig.setSeqColumn("SEQUENCE_NEXT_ID");
+ //tableConfig.setCreateTimeColumn("CREATE_TIME");
+ return tableConfig;
+ }
+
+ /**
+ * 序号生成器配置类
+ * @param tableConfig 序号表配置类
+ */
+ @Bean
+ public GeneratorConfig generatorConfig(TableConfig tableConfig) {
+ GeneratorConfig generatorConfig = new GeneratorConfig();
+ generatorConfig.setDataSource(dataSource);
+ return generatorConfig;
+ }
+
+ /**
+ * 注册序号生成器类
+ * @param generatorConfig 序号生成器配置类
+ */
+ @Bean
+ public Generator generator(GeneratorConfig generatorConfig) {
+ return new SequencesGenerator(generatorConfig);
+ }
+}
+```
+
++ 使用:
+
+```java
+package com.yang.springseq.config;
+
+import com.cdhncy.seq.generator.Generator;
+import com.cdhncy.seq.po.Sequences;
+import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class SeqTestService {
+ @Resource
+ private Generator generator;
+
+ public void test() {
+ //释放未锁定序号,此处测试,因此每次生成前都全部释放,实际使用时,建议通过定时任务,隔天释放。
+ generator.release();
+
+ //释放指定时间范围内的序号。建议使用定时任务,当天释放前天时间段的序号
+ //generator.release(beginDate,endDate);
+
+ Set set = new HashSet<>();
+ //开启多线程进行测试,实际使用时,每个业务中只需要execute()->{}里面的方法,这里是测试多个用户同时使用时是否会重复
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100));
+ for (int i = 0; i < 5; i++) {
+ threadPoolExecutor.execute(() -> {
+ Sequences sequences = generator.generate("SNT", "MISSION");
+ String formattedSeq = generator.format(sequences.getSeq(), 5, "处〔#year#〕10801#seq#");
+ generator.lock(sequences);
+ set.add(formattedSeq);
+ //打印生成的序号
+ System.out.println(formattedSeq);
+ });
+ }
+ threadPoolExecutor.shutdown();
+ while (true) {
+ if (threadPoolExecutor.isTerminated())
+ break;
+ }
+ System.out.println("共生成了:" + set.size() + "个");
+ }
+}
+
+```
+
+---
+
+TableConfig配置项,通过set方法设置(一般不用改,如果已有相同结构的表,则可以通过这些配置将原数据利用起来):
+
+| 配置项 | 类型 | 默认值 | 说明 |
+|------------------|--------|-------------|--------------------|
+| table | String | sequences | 序号表名称 |
+| keyColumn | String | key | 序号名称列名,和序号类型组成唯一主键 |
+| typeColumn | String | type | 序号类型列名,和序号名称组成唯一主键 |
+| seqColumn | String | seq | 序号值列名,和序号名称组成唯一主键 |
+| createTimeColumn | String | create_time | 创建时间列名,用于未锁定表排序 |
+
+---
+
+GeneratorConfig配置项,通过set方法设置
+
+| 配置项 | 类型 | 默认值 | 说明 |
+|---------------------|------------------------------------------------------------------|------------------|----------|
+| dataSource | javax.sql.DataSource | null | 数据源 |
+| jdbcTemplate | org.springframework.jdbc.core.JdbcTemplate | null | 数据库操作模板 |
+| transactionTemplate | org.springframework.jdbc.core.JdbcTemplate | null | 事务操作模板 |
+| transactionManager | org.springframework.jdbc.datasource.DataSourceTransactionManager | null | 事务管理器 |
+| autoCreate | Boolean | true | 开启自动建表 |
+| step | Integer | 1 | 序号增加时的步长 |
+| tableConfig | com.cdhncy.seq.config.TableConfig | TableConfig的默认配置 | 表配置 |
+
+以上配置中,jdbcTemplate和transactionTemplate优先级最高,如果jdbcTemplate、transactionTemplate、dataSource、transactionManager同时配置,则dataSource和transactionManager无效;
+进行这几种组合:dataSource+autoCreate+step+tableConfig,jdbcTemplate+transactionTemplate+autoCreate+step+tableConfig,jdbcTemplate+transactionManager+autoCreate+step+tableConfig
+
+---
+Generator方法如下:
+
+```java
+package com.cdhncy.seq.generator;
+
+import com.cdhncy.seq.po.Sequences;
+import com.cdhncy.seq.po.SequencesUnlock;
+import com.cdhncy.seq.po.SequencesUnused;
+
+import java.util.Date;
+
+public interface Generator {
+ /**
+ * 序号格式字符中的年
+ */
+ String YEAR = "#year#";
+ /**
+ * 序号格式字符中的月
+ */
+ String MONTH = "#month#";
+ /**
+ * 序号格式字符中的日
+ */
+ String DAY = "#day#";
+ /**
+ * 序号格式字符中的格式化后的序号
+ */
+ String SEQ = "#seq#";
+
+ /**
+ * 根据传入的key和type生成可用的序号对象。
+ *
+ * 如果根据key和type在{@link Sequences}中找不到记录,说明该组合的序号对象还未初次生成,返回的是seq为step的序号对象,该对象数据会写入到{@link SequencesUnlock}中。
+ *
+ * 如果根据key和type在{@link Sequences}中找到了记录,且在{@link SequencesUnused}也找到了记录,说明该组合生成的序号有部分未使用,返回的是{@link SequencesUnused}中找到的seq最小的序号对象。同时会将{@link SequencesUnused}中找到的seq最小的记录删除,然后写入到{@link SequencesUnlock}中。
+ *
+ *
+ * @param key 数据字典中的编码
+ * @param type 序号类型
+ * @return 可用的序号对象
+ */
+ Sequences generate(String key, String type);
+
+ /**
+ * 返回根据{@link #generate(String, String)}得到的序号对象,补零后的序号字符串
+ *
+ * 如生成的为3,而minLength为5,则返回的是00003
+ *
+ * @param key 数据字典中的编码
+ * @param type 序号类型
+ * @param minLength 序号数字最小长度
+ * @return 补零后的字符串
+ */
+ String generate(String key, String type, Integer minLength);
+
+ /**
+ * 将生成的序号对象格式化为指定格式
+ *
+ * pattern支持:{@link #YEAR}(当前年份)、{@link #MONTH}(当前月份)、{@link #DAY}(当前日期)、{@link #SEQ}(生成的字符串序号)四个变量
+ *
+ * seq为1,minLength为4,pattern为#year##month##day#6#seq#,则会格式化为2022013060001。此序号含义如下:
+ *
+ * 序号格式:[年][月][日][固定6开头][序号1,最小位数为4位,不足4位则补零]
+ *
+ * @param seq 需要格式化的序号
+ * @param minLength 序号最小长度,不足的会补零
+ * @param pattern 格式
+ * @return 格式化后的字符串
+ */
+ String format(Long seq, Integer minLength, String pattern);
+
+ /**
+ * 将生成的序号对象格式化为指定格式
+ *
+ * pattern支持:{@link #YEAR}(当前年份)、{@link #MONTH}(当前月份)、{@link #DAY}(当前日期)、{@link #SEQ}(生成的字符串序号)四个变量
+ *
+ * seq为1,start为6,minLength为4,pattern为#year##month##day##seq#,则会格式化为2022013060001。此序号含义如下:
+ *
+ * 序号格式:[年][月][日][固定6开头][序号1,最小位数为4位,不足4位则补零]
+ *
+ * @param seq 需要格式化的序号
+ * @param start 序号格式化后以什么字符串开头
+ * @param minLength 序号最小长度,不足的会补零
+ * @param pattern 格式
+ * @return 格式化后的字符串
+ */
+ String format(Long seq, String start, Integer minLength, String pattern);
+
+ /**
+ * 锁定指定序号,在序号生成后,调用该序号的逻辑完成后需要执行此方法
+ *
+ * 如办理案件时,先调用{@link #generate(String, String)}或者{@link #generate(String, String, Integer)}生成了序号,之后对案件进行了入库,如果入库完毕,则将该序号锁定,说明这个序号已被使用
+ *
+ * 注意,此处的锁定非数据库中锁定,而是{@link SequencesUnused}和{@link SequencesUnlock}中均不存在key、type、seq相同的记录视为锁定。因此此处实际是把这两个表中的记录均删除了
+ *
+ * @param sequences 需要锁定的序号
+ * @return 锁定结果
+ */
+ boolean lock(Sequences sequences);
+
+ /**
+ * 释放所有未使用的序号
+ *
+ * {@link SequencesUnlock}中未通过{@link #lock(Sequences)}方法锁定的序号会一直存在,调用此方法会将里面的所有序号都移动到{@link SequencesUnused}中,下次生成序号时优先从{@link SequencesUnused}获取。
+ */
+ void release();
+
+ /**
+ * 释放指定时间段内未使用的序号
+ *
+ * {@link SequencesUnlock}中未通过{@link #lock(Sequences)}方法锁定的序号会一直存在,调用此方法会将里面的所有序号都移动到{@link SequencesUnused}中,下次生成序号时优先从{@link SequencesUnused}获取。
+ *
+ * @param begin 开始时间
+ * @param end 结束时间
+ */
+ void release(Date begin, Date end);
+}
+
+```
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..f18d702
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+ com.cdhncy
+ seq
+ 1.0.0
+ seq
+ seq
+
+ 1.8
+
+
+
+ org.springframework
+ spring-jdbc
+ 5.3.15
+
+
+ mysql
+ mysql-connector-java
+ 8.0.28
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
diff --git a/src/main/java/com/cdhncy/seq/config/GeneratorConfig.java b/src/main/java/com/cdhncy/seq/config/GeneratorConfig.java
new file mode 100644
index 0000000..087ad37
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/config/GeneratorConfig.java
@@ -0,0 +1,115 @@
+package com.cdhncy.seq.config;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.sql.DataSource;
+
+/**
+ * 生成器配置
+ *
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class GeneratorConfig {
+ /**
+ * 数据源
+ */
+ private DataSource dataSource;
+
+ /**
+ * 数据库操作模板
+ */
+ private JdbcTemplate jdbcTemplate;
+
+ /**
+ * 事务处理模板
+ */
+ private TransactionTemplate transactionTemplate;
+
+ /**
+ * 事务管理器
+ */
+ private DataSourceTransactionManager transactionManager;
+
+ /**
+ * 自动创建表
+ */
+ private Boolean autoCreate = true;
+
+ /**
+ * 序号每次增加的步长
+ */
+ private Integer step = 1;
+
+ /**
+ * 表和字段配置
+ */
+ private TableConfig tableConfig = new TableConfig();
+
+ public GeneratorConfig() {
+ }
+
+ public GeneratorConfig(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ public void setDataSource(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public JdbcTemplate getJdbcTemplate() {
+ return jdbcTemplate;
+ }
+
+ public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ public TransactionTemplate getTransactionTemplate() {
+ return transactionTemplate;
+ }
+
+ public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
+ this.transactionTemplate = transactionTemplate;
+ }
+
+ public DataSourceTransactionManager getTransactionManager() {
+ return transactionManager;
+ }
+
+ public void setTransactionManager(DataSourceTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ public Boolean getAutoCreate() {
+ return autoCreate;
+ }
+
+ public void setAutoCreate(Boolean autoCreate) {
+ this.autoCreate = autoCreate;
+ }
+
+ public Integer getStep() {
+ return step;
+ }
+
+ public void setStep(Integer step) {
+ if (step == 0)
+ step = 1;
+ this.step = step;
+ }
+
+ public TableConfig getTableConfig() {
+ return tableConfig;
+ }
+
+ public void setTableConfig(TableConfig tableConfig) {
+ this.tableConfig = tableConfig;
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/config/TableConfig.java b/src/main/java/com/cdhncy/seq/config/TableConfig.java
new file mode 100644
index 0000000..cd84762
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/config/TableConfig.java
@@ -0,0 +1,74 @@
+package com.cdhncy.seq.config;
+
+/**
+ * 生成器对应的数据库表和字段配置
+ *
+ * @author yanghuanglin
+ * @since 2022/1/30
+ */
+public class TableConfig {
+ /**
+ * 当前序号表名,闲置序号表会在该名称后增加后缀_unused,未锁定序号表会在该名称后增加unlock
+ */
+ private String table = "sequences";
+
+ /**
+ * 序号英文名称,和序号类型组成唯一组件
+ */
+ private String keyColumn = "key";
+
+ /**
+ * 序号类型
+ */
+ private String typeColumn = "type";
+
+ /**
+ * 序号值
+ */
+ private String seqColumn = "seq";
+
+ /**
+ * 未锁定序号使用时间
+ */
+ private String createTimeColumn = "create_time";
+
+ public String getTable() {
+ return table;
+ }
+
+ public void setTable(String table) {
+ this.table = table.toLowerCase();
+ }
+
+ public String getKeyColumn() {
+ return keyColumn;
+ }
+
+ public void setKeyColumn(String keyColumn) {
+ this.keyColumn = keyColumn.toLowerCase();
+ }
+
+ public String getTypeColumn() {
+ return typeColumn;
+ }
+
+ public void setTypeColumn(String typeColumn) {
+ this.typeColumn = typeColumn.toLowerCase();
+ }
+
+ public String getSeqColumn() {
+ return seqColumn;
+ }
+
+ public void setSeqColumn(String seqColumn) {
+ this.seqColumn = seqColumn.toLowerCase();
+ }
+
+ public String getCreateTimeColumn() {
+ return createTimeColumn;
+ }
+
+ public void setCreateTimeColumn(String createTimeColumn) {
+ this.createTimeColumn = createTimeColumn.toLowerCase();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/cdhncy/seq/dao/SequencesDao.java b/src/main/java/com/cdhncy/seq/dao/SequencesDao.java
new file mode 100644
index 0000000..2a771a9
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/SequencesDao.java
@@ -0,0 +1,26 @@
+package com.cdhncy.seq.dao;
+
+import com.cdhncy.seq.po.Sequences;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public interface SequencesDao {
+ /**
+ * 查找最后被使用的序号
+ */
+ Sequences find(Sequences sequences);
+
+ /**
+ * 保存新生成的序号
+ */
+ boolean save(Sequences sequences);
+
+ /**
+ * 更新被使用的序号
+ */
+ boolean update(Sequences sequences);
+
+ void createTable();
+}
diff --git a/src/main/java/com/cdhncy/seq/dao/SequencesUnlockDao.java b/src/main/java/com/cdhncy/seq/dao/SequencesUnlockDao.java
new file mode 100644
index 0000000..07bfa61
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/SequencesUnlockDao.java
@@ -0,0 +1,44 @@
+package com.cdhncy.seq.dao;
+
+import com.cdhncy.seq.po.SequencesUnlock;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public interface SequencesUnlockDao {
+ /**
+ * 保存使用中的序号
+ */
+ boolean save(SequencesUnlock sequencesUnlock);
+
+ /**
+ * 删除使用中的序号
+ */
+ boolean delete(SequencesUnlock sequencesUnlock);
+
+ /**
+ * 列出所有使用中的序号
+ */
+ List listAll();
+
+ /**
+ * 列出指定时间段内使用中的序号
+ */
+ List listByDate(Date begin, Date end);
+
+ /**
+ * 删除所有使用中的序号
+ */
+ boolean deleteAll();
+
+ /**
+ * 删除指定时间段内使用中的序号
+ */
+ boolean deleteByDate(Date begin, Date end);
+
+ void createTable();
+}
diff --git a/src/main/java/com/cdhncy/seq/dao/SequencesUnusedDao.java b/src/main/java/com/cdhncy/seq/dao/SequencesUnusedDao.java
new file mode 100644
index 0000000..166abfe
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/SequencesUnusedDao.java
@@ -0,0 +1,34 @@
+package com.cdhncy.seq.dao;
+
+import com.cdhncy.seq.po.SequencesUnused;
+
+import java.util.List;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public interface SequencesUnusedDao {
+
+ /**
+ * 根据key,type查找seq最小的空闲序号
+ */
+ SequencesUnused findMinSeq(SequencesUnused sequencesUnused);
+
+ /**
+ * 根据key,type查找seq最大的空闲序号
+ */
+ SequencesUnused findMaxSeq(SequencesUnused sequencesUnused);
+
+ /**
+ * 根据key,type,seq删除空闲序号
+ */
+ boolean delete(SequencesUnused sequencesUnused);
+
+ /**
+ * 批量保存空闲序号
+ */
+ boolean saveBatch(List sequencesUnusedList);
+
+ void createTable();
+}
diff --git a/src/main/java/com/cdhncy/seq/dao/impl/SequencesDaoImpl.java b/src/main/java/com/cdhncy/seq/dao/impl/SequencesDaoImpl.java
new file mode 100644
index 0000000..ca2c237
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/impl/SequencesDaoImpl.java
@@ -0,0 +1,72 @@
+package com.cdhncy.seq.dao.impl;
+
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.dao.SequencesDao;
+import com.cdhncy.seq.po.Sequences;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SequencesDaoImpl implements SequencesDao {
+ private final JdbcTemplate jdbcTemplate;
+ private final TableConfig tableConfig;
+
+ public SequencesDaoImpl(JdbcTemplate jdbcTemplate, TableConfig tableConfig) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.tableConfig = tableConfig;
+ }
+
+ @Override
+ public Sequences find(Sequences sequences) {
+ String sql = "select * from `%s` where `%s`=? and `%s`=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn());
+ try {
+ return this.jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
+ Sequences result = new Sequences();
+ result.setKey(rs.getString(tableConfig.getKeyColumn()));
+ result.setType(rs.getString(tableConfig.getTypeColumn()));
+ result.setSeq(rs.getLong(tableConfig.getSeqColumn()));
+ return result;
+ }, sequences.getKey(), sequences.getType());
+ } catch (EmptyResultDataAccessException ignored) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean save(Sequences sequences) {
+ String sql = "insert into `%s`(`%s`,`%s`,`%s`) values(?,?,?)";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ int result = this.jdbcTemplate.update(sql, sequences.getKey(), sequences.getType(), sequences.getSeq());
+ return result != 0;
+ }
+
+ @Override
+ public boolean update(Sequences sequences) {
+ String sql = "update `%s` set `%s`=? where `%s`=? and `%s`=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getSeqColumn(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn());
+ int result = this.jdbcTemplate.update(sql, sequences.getSeq(), sequences.getKey(), sequences.getType());
+ return result != 0;
+ }
+
+ @Override
+ public void createTable() {
+ String sql = "CREATE TABLE IF NOT EXISTS `%s` ( " +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号英文名称'," +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号类型'," +
+ " `%s` BIGINT ( 2 ) NOT NULL COMMENT '已使用到的序号'," +
+ " PRIMARY KEY ( `%s`, `%s` ) " +
+ " ) COMMENT '当前序号表'";
+ sql = String.format(sql, tableConfig.getTable(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn(),
+ tableConfig.getSeqColumn(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn());
+ this.jdbcTemplate.execute(sql);
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnlockDaoImpl.java b/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnlockDaoImpl.java
new file mode 100644
index 0000000..fa67e0c
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnlockDaoImpl.java
@@ -0,0 +1,101 @@
+package com.cdhncy.seq.dao.impl;
+
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.dao.SequencesUnlockDao;
+import com.cdhncy.seq.po.SequencesUnlock;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SequencesUnlockDaoImpl implements SequencesUnlockDao {
+ private final JdbcTemplate jdbcTemplate;
+ private final TableConfig tableConfig;
+
+ public SequencesUnlockDaoImpl(JdbcTemplate jdbcTemplate, TableConfig tableConfig) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.tableConfig = tableConfig;
+ }
+
+ @Override
+ public boolean save(SequencesUnlock sequencesUnlock) {
+ String sql = "insert into `%s_unlock`(`%s`,`%s`,`%s`,`%s`) values(?,?,?,?)";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn(), tableConfig.getCreateTimeColumn());
+ int result = this.jdbcTemplate.update(sql, sequencesUnlock.getKey(), sequencesUnlock.getType(), sequencesUnlock.getSeq(), sequencesUnlock.getCreateTime());
+ return result != 0;
+ }
+
+ @Override
+ public boolean delete(SequencesUnlock sequencesUnlock) {
+ String sql = "delete from `%s_unlock` where `%s`=? and `%s`=? and `%s`=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ int result = this.jdbcTemplate.update(sql, sequencesUnlock.getKey(), sequencesUnlock.getType(), sequencesUnlock.getSeq());
+ return result != 0;
+ }
+
+ @Override
+ public List listAll() {
+ String sql = "select * from `%s_unlock`";
+ sql = String.format(sql, tableConfig.getTable());
+ return this.jdbcTemplate.query(sql, rowMapper());
+ }
+
+ @Override
+ public List listByDate(Date begin, Date end) {
+ String sql = "select * from `%s_unlock` where `%s`>=? and `%s`<=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getCreateTimeColumn(), tableConfig.getCreateTimeColumn());
+ return this.jdbcTemplate.query(sql, rowMapper(), begin, end);
+ }
+
+ @Override
+ public boolean deleteAll() {
+ String sql = "delete from `%s_unlock`";
+ sql = String.format(sql, tableConfig.getTable());
+ int result = this.jdbcTemplate.update(sql);
+ return result != 0;
+ }
+
+ @Override
+ public boolean deleteByDate(Date begin, Date end) {
+ String sql = "delete from `%s_unlock` where `%s`>=? and `%s`<=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getCreateTimeColumn(), tableConfig.getCreateTimeColumn());
+ int result = this.jdbcTemplate.update(sql, begin, end);
+ return result != 0;
+ }
+
+ @Override
+ public void createTable() {
+ String sql = "CREATE TABLE IF NOT EXISTS `%s_unlock` ( " +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号英文名称'," +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号类型'," +
+ " `%s` BIGINT ( 2 ) NOT NULL COMMENT '尚未锁定的序号'," +
+ " `%s` DATETIME NOT NULL COMMENT '使用时间'," +
+ " PRIMARY KEY ( `%s`, `%s` ,`%s` ) " +
+ " ) COMMENT '未锁定序号表'";
+ sql = String.format(sql, tableConfig.getTable(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn(),
+ tableConfig.getSeqColumn(),
+ tableConfig.getCreateTimeColumn(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn(),
+ tableConfig.getSeqColumn());
+ this.jdbcTemplate.execute(sql);
+ }
+
+ private RowMapper rowMapper() {
+ return (rs, rowNum) -> {
+ SequencesUnlock sequencesUnlock = new SequencesUnlock();
+ sequencesUnlock.setKey(rs.getString(tableConfig.getKeyColumn()));
+ sequencesUnlock.setType(rs.getString(tableConfig.getTypeColumn()));
+ sequencesUnlock.setSeq(rs.getLong(tableConfig.getSeqColumn()));
+ sequencesUnlock.setCreateTime(rs.getDate(tableConfig.getCreateTimeColumn()));
+ return sequencesUnlock;
+ };
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnusedDaoImpl.java b/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnusedDaoImpl.java
new file mode 100644
index 0000000..0f220b6
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/dao/impl/SequencesUnusedDaoImpl.java
@@ -0,0 +1,106 @@
+package com.cdhncy.seq.dao.impl;
+
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.dao.SequencesUnusedDao;
+import com.cdhncy.seq.po.SequencesUnused;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SequencesUnusedDaoImpl implements SequencesUnusedDao {
+ private final JdbcTemplate jdbcTemplate;
+ private final TableConfig tableConfig;
+
+ public SequencesUnusedDaoImpl(JdbcTemplate jdbcTemplate, TableConfig tableConfig) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.tableConfig = tableConfig;
+ }
+
+ @Override
+ public SequencesUnused findMinSeq(SequencesUnused sequencesUnused) {
+ String sql = "select * from `%s_ununsed` where `%s`=? and `%s`=? order by `%s` asc limit 0,1";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ try {
+ return this.jdbcTemplate.queryForObject(sql, rowMapper(), sequencesUnused.getKey(), sequencesUnused.getType());
+ } catch (EmptyResultDataAccessException ignored) {
+ return null;
+ }
+ }
+
+ @Override
+ public SequencesUnused findMaxSeq(SequencesUnused sequencesUnused) {
+ try {
+ String sql = "select * from `%s_ununsed` where `%s`=? and `%s`=? order by `%s` desc limit 0,1";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ return this.jdbcTemplate.queryForObject(sql, rowMapper(), sequencesUnused.getKey(), sequencesUnused.getType());
+ } catch (EmptyResultDataAccessException ignored) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean delete(SequencesUnused sequencesUnused) {
+ String sql = "delete from `%s_ununsed` where `%s`=? and `%s`=? and `%s`=?";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ int result = this.jdbcTemplate.update(sql, sequencesUnused.getKey(), sequencesUnused.getType(), sequencesUnused.getSeq());
+ return result != 0;
+ }
+
+ @Override
+ public boolean saveBatch(List sequencesUnusedList) {
+ String sql = "insert into `%s_ununsed`(`%s`,`%s`,`%s`) values(?,?,?)";
+ sql = String.format(sql, tableConfig.getTable(), tableConfig.getKeyColumn(), tableConfig.getTypeColumn(), tableConfig.getSeqColumn());
+ int[] result = this.jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
+ @Override
+ public void setValues(PreparedStatement ps, int i) throws SQLException {
+ SequencesUnused sequencesUnused = sequencesUnusedList.get(i);
+ ps.setString(1, sequencesUnused.getKey());
+ ps.setString(2, sequencesUnused.getType());
+ ps.setLong(3, sequencesUnused.getSeq());
+ }
+
+ @Override
+ public int getBatchSize() {
+ return sequencesUnusedList.size();
+ }
+ });
+ return result.length != 0;
+ }
+
+ @Override
+ public void createTable() {
+ String sql = "CREATE TABLE IF NOT EXISTS `%s_ununsed` ( " +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号英文名称'," +
+ " `%s` VARCHAR ( 255 ) NOT NULL COMMENT '序号类型'," +
+ " `%s` BIGINT ( 2 ) NOT NULL COMMENT '闲置的的序号'," +
+ " PRIMARY KEY ( `%s`, `%s`, `%s` ) " +
+ " ) COMMENT '闲置序号表'";
+ sql = String.format(sql, tableConfig.getTable(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn(),
+ tableConfig.getSeqColumn(),
+ tableConfig.getKeyColumn(),
+ tableConfig.getTypeColumn(),
+ tableConfig.getSeqColumn());
+ this.jdbcTemplate.execute(sql);
+ }
+
+ private RowMapper rowMapper() {
+ return (rs, rowNum) -> {
+ SequencesUnused result = new SequencesUnused();
+ result.setKey(rs.getString(tableConfig.getKeyColumn()));
+ result.setType(rs.getString(tableConfig.getTypeColumn()));
+ result.setSeq(rs.getLong(tableConfig.getSeqColumn()));
+ return result;
+ };
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/generator/Generator.java b/src/main/java/com/cdhncy/seq/generator/Generator.java
new file mode 100644
index 0000000..09eeb83
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/generator/Generator.java
@@ -0,0 +1,114 @@
+package com.cdhncy.seq.generator;
+
+import com.cdhncy.seq.po.Sequences;
+import com.cdhncy.seq.po.SequencesUnlock;
+import com.cdhncy.seq.po.SequencesUnused;
+
+import java.util.Date;
+
+public interface Generator {
+ /**
+ * 序号格式字符中的年
+ */
+ String YEAR = "#year#";
+ /**
+ * 序号格式字符中的月
+ */
+ String MONTH = "#month#";
+ /**
+ * 序号格式字符中的日
+ */
+ String DAY = "#day#";
+ /**
+ * 序号格式字符中的格式化后的序号
+ */
+ String SEQ = "#seq#";
+
+ /**
+ * 根据传入的key和type生成可用的序号对象。
+ *
+ * 如果根据key和type在{@link Sequences}中找不到记录,说明该组合的序号对象还未初次生成,返回的是seq为step的序号对象,该对象数据会写入到{@link SequencesUnlock}中。
+ *
+ * 如果根据key和type在{@link Sequences}中找到了记录,且在{@link SequencesUnused}也找到了记录,说明该组合生成的序号有部分未使用,返回的是{@link SequencesUnused}中找到的seq最小的序号对象。同时会将{@link SequencesUnused}中找到的seq最小的记录删除,然后写入到{@link SequencesUnlock}中。
+ *
+ *
+ * @param key 数据字典中的编码
+ * @param type 序号类型
+ * @return 可用的序号对象
+ */
+ Sequences generate(String key, String type);
+
+ /**
+ * 返回根据{@link #generate(String, String)}得到的序号对象,补零后的序号字符串
+ *
+ * 如生成的为3,而minLength为5,则返回的是00003
+ *
+ * @param key 数据字典中的编码
+ * @param type 序号类型
+ * @param minLength 序号数字最小长度
+ * @return 补零后的字符串
+ */
+ String generate(String key, String type, Integer minLength);
+
+ /**
+ * 将生成的序号对象格式化为指定格式
+ *
+ * pattern支持:{@link #YEAR}(当前年份)、{@link #MONTH}(当前月份)、{@link #DAY}(当前日期)、{@link #SEQ}(生成的字符串序号)四个变量
+ *
+ * seq为1,minLength为4,pattern为#year##month##day#6#seq#,则会格式化为2022013060001。此序号含义如下:
+ *
+ * 序号格式:[年][月][日][固定6开头][序号1,最小位数为4位,不足4位则补零]
+ *
+ * @param seq 需要格式化的序号
+ * @param minLength 序号最小长度,不足的会补零
+ * @param pattern 格式
+ * @return 格式化后的字符串
+ */
+ String format(Long seq, Integer minLength, String pattern);
+
+ /**
+ * 将生成的序号对象格式化为指定格式
+ *
+ * pattern支持:{@link #YEAR}(当前年份)、{@link #MONTH}(当前月份)、{@link #DAY}(当前日期)、{@link #SEQ}(生成的字符串序号)四个变量
+ *
+ * seq为1,start为6,minLength为4,pattern为#year##month##day##seq#,则会格式化为2022013060001。此序号含义如下:
+ *
+ * 序号格式:[年][月][日][固定6开头][序号1,最小位数为4位,不足4位则补零]
+ *
+ * @param seq 需要格式化的序号
+ * @param start 序号格式化后以什么字符串开头
+ * @param minLength 序号最小长度,不足的会补零
+ * @param pattern 格式
+ * @return 格式化后的字符串
+ */
+ String format(Long seq, String start, Integer minLength, String pattern);
+
+ /**
+ * 锁定指定序号,在序号生成后,调用该序号的逻辑完成后需要执行此方法
+ *
+ * 如办理案件时,先调用{@link #generate(String, String)}或者{@link #generate(String, String, Integer)}生成了序号,之后对案件进行了入库,如果入库完毕,则将该序号锁定,说明这个序号已被使用
+ *
+ * 注意,此处的锁定非数据库中锁定,而是{@link SequencesUnused}和{@link SequencesUnlock}中均不存在key、type、seq相同的记录视为锁定。因此此处实际是把这两个表中的记录均删除了
+ *
+ * @param sequences 需要锁定的序号
+ * @return 锁定结果
+ */
+ boolean lock(Sequences sequences);
+
+ /**
+ * 释放所有未使用的序号
+ *
+ * {@link SequencesUnlock}中未通过{@link #lock(Sequences)}方法锁定的序号会一直存在,调用此方法会将里面的所有序号都移动到{@link SequencesUnused}中,下次生成序号时优先从{@link SequencesUnused}获取。
+ */
+ void release();
+
+ /**
+ * 释放指定时间段内未使用的序号
+ *
+ * {@link SequencesUnlock}中未通过{@link #lock(Sequences)}方法锁定的序号会一直存在,调用此方法会将里面的所有序号都移动到{@link SequencesUnused}中,下次生成序号时优先从{@link SequencesUnused}获取。
+ *
+ * @param begin 开始时间
+ * @param end 结束时间
+ */
+ void release(Date begin, Date end);
+}
diff --git a/src/main/java/com/cdhncy/seq/generator/impl/SequencesGenerator.java b/src/main/java/com/cdhncy/seq/generator/impl/SequencesGenerator.java
new file mode 100644
index 0000000..9acdc9c
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/generator/impl/SequencesGenerator.java
@@ -0,0 +1,188 @@
+package com.cdhncy.seq.generator.impl;
+
+import com.cdhncy.seq.config.GeneratorConfig;
+import com.cdhncy.seq.dao.SequencesDao;
+import com.cdhncy.seq.dao.SequencesUnusedDao;
+import com.cdhncy.seq.dao.SequencesUnlockDao;
+import com.cdhncy.seq.dao.impl.SequencesDaoImpl;
+import com.cdhncy.seq.dao.impl.SequencesUnusedDaoImpl;
+import com.cdhncy.seq.dao.impl.SequencesUnlockDaoImpl;
+import com.cdhncy.seq.po.Sequences;
+import com.cdhncy.seq.po.SequencesUnused;
+import com.cdhncy.seq.po.SequencesUnlock;
+import com.cdhncy.seq.generator.Generator;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.support.JdbcTransactionManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import javax.sql.DataSource;
+import java.util.*;
+
+public class SequencesGenerator implements Generator {
+ private final TransactionTemplate transactionTemplate;
+ private final SequencesDao sequencesDao;
+ private final SequencesUnusedDao sequencesUnusedDao;
+ private final SequencesUnlockDao sequencesUnlockDao;
+ private final Integer step;
+
+ public SequencesGenerator(GeneratorConfig generatorConfig) {
+ //数据库操作模板
+ JdbcTemplate jdbcTemplate = generatorConfig.getJdbcTemplate();
+
+ if (jdbcTemplate == null) {
+ //数据源
+ DataSource dataSource = generatorConfig.getDataSource();
+ if (dataSource == null)
+ //若数据库操作模板为空,也没有配置数据源,则抛出异常
+ throw new NullPointerException("数据源不能为空");
+ //否则以数据源创建数据库操作模板
+ jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ if (generatorConfig.getTransactionTemplate() == null) {
+ //若没有配置事务操作模板,则从配置中取事务管理器
+ DataSourceTransactionManager transactionManager = generatorConfig.getTransactionManager();
+ if (transactionManager == null) {
+ //若未配置事务管理器,则通过数据源新建
+ DataSource dataSource = jdbcTemplate.getDataSource();
+ if (dataSource == null)
+ throw new NullPointerException("数据源不能为空");
+ transactionManager = new JdbcTransactionManager(dataSource);
+ }
+ //通过事务管理器创建事务操作模板
+ transactionTemplate = new TransactionTemplate(transactionManager);
+ } else {
+ //获取事务操作模板
+ transactionTemplate = generatorConfig.getTransactionTemplate();
+ }
+
+ this.sequencesDao = new SequencesDaoImpl(jdbcTemplate, generatorConfig.getTableConfig());
+ this.sequencesUnusedDao = new SequencesUnusedDaoImpl(jdbcTemplate, generatorConfig.getTableConfig());
+ this.sequencesUnlockDao = new SequencesUnlockDaoImpl(jdbcTemplate, generatorConfig.getTableConfig());
+ this.step = generatorConfig.getStep();
+
+ autoCreateTable(generatorConfig.getAutoCreate());
+ }
+
+ /**
+ * 自动创建需要的表
+ */
+ private void autoCreateTable(Boolean autoCreate) {
+ if (!autoCreate)
+ return;
+ this.sequencesDao.createTable();
+ this.sequencesUnusedDao.createTable();
+ this.sequencesUnlockDao.createTable();
+ }
+
+ @Override
+ public synchronized Sequences generate(String key, String type) {
+ return transactionTemplate.execute(status -> {
+ try {
+ //根据传入的key和type新生成查询条件对象
+ Sequences condition = new Sequences(key, type);
+
+ //找到正在使用的最大序号
+ Sequences sequences = sequencesDao.find(condition);
+ if (sequences == null) {
+ //不存在,说明还没生成,将新生成的入库,此时序号为默认的0
+ sequences = condition;
+ sequencesDao.save(sequences);
+ }
+
+ //根据传入的key和type查找空闲编号最小的一个
+ SequencesUnused conditionIdle = new SequencesUnused(condition);
+ SequencesUnused sequencesUnused = sequencesUnusedDao.findMinSeq(conditionIdle);
+
+ if (sequencesUnused == null) {
+ //空闲编号不存在,说明是未生成过,序号需要增加后直接使用,同时将新生成的写入到使用中表
+ sequences.increase(step);
+ SequencesUnlock sequencesUnlock = new SequencesUnlock(sequences);
+ sequencesUnlock.setCreateTime(new Date());
+
+ sequencesDao.update(sequences);
+ sequencesUnlockDao.save(sequencesUnlock);
+ } else {
+ //空闲编号存在,说明已经生成过,序号不需要增加,直接使用。同时将该空闲编号移动到使用中表
+ sequences = new Sequences(sequencesUnused);
+ SequencesUnlock sequencesUnlock = new SequencesUnlock(sequencesUnused);
+ sequencesUnlock.setCreateTime(new Date());
+
+ sequencesUnlockDao.save(sequencesUnlock);
+ sequencesUnusedDao.delete(sequencesUnused);
+ }
+ return sequences;
+ } catch (Exception e) {
+ e.printStackTrace();
+ status.setRollbackOnly();
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public synchronized String generate(String key, String type, Integer minLength) {
+ Sequences sequences = this.generate(key, type);
+ if (sequences == null)
+ return null;
+ return sequences.format(minLength);
+ }
+
+ @Override
+ public String format(Long seq, Integer minLength, String pattern) {
+ return format(seq, null, minLength, pattern);
+ }
+
+ @Override
+ public String format(Long seq, String start, Integer minLength, String pattern) {
+ if (start == null)
+ start = "";
+ String seqString = start + new Sequences(seq).format(minLength);
+ Calendar calendar = Calendar.getInstance();
+ pattern = pattern.replaceAll(Generator.YEAR, String.valueOf(calendar.get(Calendar.YEAR)));
+ pattern = pattern.replaceAll(Generator.MONTH, String.format("%02d", calendar.get(Calendar.MONTH) + 1));
+ pattern = pattern.replaceAll(Generator.DAY, String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
+ pattern = pattern.replaceAll(Generator.SEQ, seqString);
+ return pattern;
+ }
+
+ @Override
+ public synchronized boolean lock(Sequences sequences) {
+ SequencesUnlock condition = new SequencesUnlock(sequences);
+ //将使用中表的对应数据删除,空闲表中数据在生成时会删除,因此这里不需要处理该表
+ return sequencesUnlockDao.delete(condition);
+ }
+
+ @Override
+ public void release() {
+ //列出所有使用中表存在的序号
+ List sequencesUnlockList = sequencesUnlockDao.listAll();
+
+ List sequencesUnusedList = new ArrayList<>();
+ for (SequencesUnlock sequencesUnlock : sequencesUnlockList) {
+ sequencesUnusedList.add(new SequencesUnused(sequencesUnlock));
+ }
+
+ //将使用中表的序号放到空闲表中
+ sequencesUnusedDao.saveBatch(sequencesUnusedList);
+ //删除所有使用中表的数据
+ sequencesUnlockDao.deleteAll();
+ }
+
+ @Override
+ public void release(Date begin, Date end) {
+ //列出指定时间段内使用中表存在的序号
+ List sequencesUnlockList = sequencesUnlockDao.listByDate(begin, end);
+
+ List sequencesUnusedList = new ArrayList<>();
+ for (SequencesUnlock sequencesUnlock : sequencesUnlockList) {
+ sequencesUnusedList.add(new SequencesUnused(sequencesUnlock));
+ }
+
+ //将指定时间段内使用中表的序号放到空闲表中
+ sequencesUnusedDao.saveBatch(sequencesUnusedList);
+ //删除指定时间段内使用中表的数据
+ sequencesUnlockDao.deleteByDate(begin, end);
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/po/Sequences.java b/src/main/java/com/cdhncy/seq/po/Sequences.java
new file mode 100644
index 0000000..52ed7a2
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/po/Sequences.java
@@ -0,0 +1,88 @@
+package com.cdhncy.seq.po;
+
+
+/**
+ * 当前序号
+ *
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class Sequences {
+ /**
+ * 需要生成序号的key,和type组成唯一主键
+ */
+ protected String key;
+
+ /**
+ * 需要生成序号的类型,和key组成唯一主键
+ */
+ protected String type;
+
+ /**
+ * 默认序号
+ */
+ protected Long seq = 0L;
+
+ public Sequences() {
+ }
+
+ public Sequences(Long seq) {
+ this.seq = seq;
+ }
+
+ public Sequences(String key, String type) {
+ this.key = key;
+ this.type = type;
+ }
+
+ public Sequences(SequencesUnused sequencesUnused) {
+ this.key = sequencesUnused.getKey();
+ this.type = sequencesUnused.getType();
+ this.seq = sequencesUnused.getSeq();
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public Long getSeq() {
+ return seq;
+ }
+
+ public void setSeq(Long seq) {
+ this.seq = seq;
+ }
+
+ /**
+ * 将序号增加指定步长
+ *
+ * @param step 步长
+ */
+ public void increase(Integer step) {
+ this.seq += step;
+ }
+
+ /**
+ * 序号补零
+ *
+ * @param minLength 最小长度,低于此长度,会填充零
+ * @return 补零后的序号
+ */
+ public String format(Integer minLength) {
+ if (minLength != null)
+ return String.format("%0" + minLength + "d", this.seq);
+ return String.valueOf(this.seq);
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/po/SequencesUnlock.java b/src/main/java/com/cdhncy/seq/po/SequencesUnlock.java
new file mode 100644
index 0000000..24dd381
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/po/SequencesUnlock.java
@@ -0,0 +1,37 @@
+package com.cdhncy.seq.po;
+
+
+import java.util.Date;
+
+/**
+ * 未锁定序号
+ *
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SequencesUnlock extends Sequences {
+ private Date createTime;
+
+ public SequencesUnlock() {
+ }
+
+ public SequencesUnlock(Sequences sequences) {
+ this.key = sequences.getKey();
+ this.type = sequences.getType();
+ this.seq = sequences.getSeq();
+ }
+
+ public SequencesUnlock(SequencesUnused sequencesUnused) {
+ this.key = sequencesUnused.getKey();
+ this.type = sequencesUnused.getType();
+ this.seq = sequencesUnused.getSeq();
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+}
diff --git a/src/main/java/com/cdhncy/seq/po/SequencesUnused.java b/src/main/java/com/cdhncy/seq/po/SequencesUnused.java
new file mode 100644
index 0000000..a85182d
--- /dev/null
+++ b/src/main/java/com/cdhncy/seq/po/SequencesUnused.java
@@ -0,0 +1,19 @@
+package com.cdhncy.seq.po;
+
+
+/**
+ * 闲置序号
+ *
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SequencesUnused extends Sequences {
+ public SequencesUnused() {
+ }
+
+ public SequencesUnused(Sequences sequences) {
+ this.key = sequences.getKey();
+ this.type = sequences.getType();
+ this.seq = sequences.getSeq();
+ }
+}
diff --git a/src/test/java/SeqTest.java b/src/test/java/SeqTest.java
new file mode 100644
index 0000000..5b99676
--- /dev/null
+++ b/src/test/java/SeqTest.java
@@ -0,0 +1,76 @@
+import com.cdhncy.seq.config.GeneratorConfig;
+import com.cdhncy.seq.config.TableConfig;
+import com.cdhncy.seq.po.Sequences;
+import com.cdhncy.seq.generator.Generator;
+import com.cdhncy.seq.generator.impl.SequencesGenerator;
+import com.mysql.cj.jdbc.MysqlDataSource;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author yanghuanglin
+ * @since 2022/1/28
+ */
+public class SeqTest {
+ private static final MysqlDataSource dataSource = new MysqlDataSource();
+ private static final Generator generator;
+
+ static {
+ dataSource.setURL("jdbc:mysql://127.0.0.1:3306/sequence");
+ dataSource.setUser("root");
+ dataSource.setPassword("root");
+
+ GeneratorConfig generatorConfig = new GeneratorConfig(dataSource);
+ TableConfig tableConfig = new TableConfig();
+// tableConfig.setTable("sequences");
+// tableConfig.setKeyColumn("SEQUENCE_KEY");
+// tableConfig.setTypeColumn("SEQUENCE_TYPE");
+// tableConfig.setSeqColumn("SEQUENCE_NEXT_ID");
+ generatorConfig.setTableConfig(tableConfig);
+
+ generator = new SequencesGenerator(generatorConfig);
+ }
+
+ @Test
+ public void generateTest() {
+ //释放未锁定序列号
+ generator.release();
+
+ Set set = new HashSet<>();
+ ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100));
+ for (int i = 0; i < 5; i++) {
+ int finalI = i;
+ threadPoolExecutor.execute(() -> {
+ Sequences sequences = generator.generate("SNT", "MISSION");
+ String formattedSeq = generator.format(sequences.getSeq(), 5, "处〔#year#〕10801#seq#");
+// if (finalI % 2 == 0)
+// System.out.println(3 / 0);
+ generator.lock(sequences);
+ set.add(formattedSeq);
+ System.out.println(formattedSeq);
+ });
+ }
+ threadPoolExecutor.shutdown();
+ while (true) {
+ if (threadPoolExecutor.isTerminated())
+ break;
+ }
+ System.out.println(set.size());
+ }
+
+ @Test
+ public void releaseTest() {
+ generator.release();
+ }
+
+ @Test
+ public void formatTest() {
+ String s = "select * from sequences where `%s`=? and `%s`=?";
+ System.out.println(String.format(s, "key", "value"));
+ }
+}