From fa44f4427dae46dbf503fc3cf238d40791df9f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=BB=84=E6=9E=97?= Date: Tue, 1 Feb 2022 11:45:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 405 +++++++++++++++++- pom.xml | 57 +++ .../cdhncy/seq/config/GeneratorConfig.java | 115 +++++ .../com/cdhncy/seq/config/TableConfig.java | 74 ++++ .../java/com/cdhncy/seq/dao/SequencesDao.java | 26 ++ .../cdhncy/seq/dao/SequencesUnlockDao.java | 44 ++ .../cdhncy/seq/dao/SequencesUnusedDao.java | 34 ++ .../cdhncy/seq/dao/impl/SequencesDaoImpl.java | 72 ++++ .../seq/dao/impl/SequencesUnlockDaoImpl.java | 101 +++++ .../seq/dao/impl/SequencesUnusedDaoImpl.java | 106 +++++ .../com/cdhncy/seq/generator/Generator.java | 114 +++++ .../generator/impl/SequencesGenerator.java | 188 ++++++++ .../java/com/cdhncy/seq/po/Sequences.java | 88 ++++ .../com/cdhncy/seq/po/SequencesUnlock.java | 37 ++ .../com/cdhncy/seq/po/SequencesUnused.java | 19 + src/test/java/SeqTest.java | 76 ++++ 16 files changed, 1554 insertions(+), 2 deletions(-) create mode 100644 pom.xml create mode 100644 src/main/java/com/cdhncy/seq/config/GeneratorConfig.java create mode 100644 src/main/java/com/cdhncy/seq/config/TableConfig.java create mode 100644 src/main/java/com/cdhncy/seq/dao/SequencesDao.java create mode 100644 src/main/java/com/cdhncy/seq/dao/SequencesUnlockDao.java create mode 100644 src/main/java/com/cdhncy/seq/dao/SequencesUnusedDao.java create mode 100644 src/main/java/com/cdhncy/seq/dao/impl/SequencesDaoImpl.java create mode 100644 src/main/java/com/cdhncy/seq/dao/impl/SequencesUnlockDaoImpl.java create mode 100644 src/main/java/com/cdhncy/seq/dao/impl/SequencesUnusedDaoImpl.java create mode 100644 src/main/java/com/cdhncy/seq/generator/Generator.java create mode 100644 src/main/java/com/cdhncy/seq/generator/impl/SequencesGenerator.java create mode 100644 src/main/java/com/cdhncy/seq/po/Sequences.java create mode 100644 src/main/java/com/cdhncy/seq/po/SequencesUnlock.java create mode 100644 src/main/java/com/cdhncy/seq/po/SequencesUnused.java create mode 100644 src/test/java/SeqTest.java 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")); + } +}