1. ↖回到主目录

2. 流水号生成器 KeyBuild

2.1. getKeyId()

静态方法, 获得一个随机的全局唯一流水号, 不使用数据库和Redis, 纯算法生成的唯一流水号, 长度为26位

2.2. setPrefix(String prefix)

  • @param prefix 设置流水号的前缀
  • @return KeyBuild

2.3. setSuffix(String suffix)

  • @param suffix 设置流水号的后缀
  • @return KeyBuild

2.4. setDateFormat(String dateFormat)

  • @param dateFormat 日期格式, 如yyyMMdd yyMMdd yyMM yMMdd
  • @return KeyBuild

2.5. getKey(String fieldName, int fillLength)

  • @param fieldName 字段名, 必须是DTO中存在的
  • @param fillLength 序列号的长度, 不足用数字零填充
  • @return 一个流水号 (String)

2.6. getKeys(String fieldName, int fillLength, int count)

  • @param fieldName 字段名, 必须是DTO中存在的
  • @param fillLength 序列号的长度, 不足用数字零填充
  • @param count 一次获取的流水号个数
  • @return 返回类型是 ConcurrentLinkedQueue 流水号容器

3. 流水号生成器之实战和思路解析

下面以用户表(UserDTO对应的表)为例,说明流水号生成器的使用方法

3.1. 简单流水号

  • 简单的流水号, 我们定义为 是递增的序列号
  • keyBuild()方法是 类库封装的创建 KeyBuild对象的方法.

::: 示例一

  • 假如表中只有2条数据, id 字段的值分别为 001, 002, 则执行下面程序获得的值是003
  1. package com.test.service;
  2. @Service
  3. public class KeyBuildService extends AdvancedService<UserDao, UserDTO> {
  4. public void test1() {
  5. //logger为TopFox声明的日志对象
  6. //例: 根据UserDTO中字段名id 来获取一个纯 3位数 递增的流水号
  7. logger.debug(
  8. keyBuild() //创建一个 KeyBuild对象, 会自动获取当前Service的 UserDTO 对象
  9. .getKey("id",3) //参数id 必须是 UserDTO中存在的字段
  10. ); //打印出来的值是 003
  11. }
  12. }

::: 示例二

  • 假如表中只有6条数据, id 字段的值分别为 06,07, 112,113, 2222,2223 这里有长度为2,3,4位的Id值, 执行下面的程序, debug的信息分别是08, 114, 2224.
  1. package com.test.service;
  2. @Service
  3. public class KeyBuildService extends AdvancedService<UserDao, UserDTO> {
  4. public void test2() {
  5. logger.debug(keyBuild().getKey("id",2)); //打印出来的值是 08
  6. logger.debug(keyBuild().getKey("id",3)); //打印出来的值是 114
  7. logger.debug(keyBuild().getKey("id",4)); //打印出来的值是 2224
  8. //这个例子说明是按照 id字段 值的长度隔离的.
  9. }
  10. }

总结:

  1. 流水号是通过分析当前service的UserDTO对应表的已有数据而生成的, 并将分析结果缓存到Redis中, 减少对表的读取.
  2. 流水号的生成是按照表名,字段名和已有数据的长度 隔离的
  3. 位数满后会自动增加1位, 例如获得2位数的流水号, 当99后, 再次获取会增加一位变为100
  4. 获取到流水号后, 是不会因为抛出异常而回滚, 每次调用始终 加一的. 例如 获取到 2224后抛一个异常, 事务是回滚了, 但下次获取这个流水号, 取到的是 2225(2224不会回滚).这样设计主要是考虑到”避免分布式下高并发 流水号可能会重复的问题”.
  5. 这是按照调用次数 变化的数字, 我们称之为是 “递增的次序号”. 位数不足用 0 填补

3.2. 复杂流水号(含前缀|日期|后缀)

  • 流水号 = 前缀 + 日期字符 + 递增的序列号 + 后缀
  • 如何设置 前缀和日期字符,以及后缀呢? 请看如下例子:
  1. package com.test.service;
  2. @Service
  3. public class KeyBuildService extends AdvancedService<UserDao, UserDTO> {
  4. /**
  5. * 每行数据执行本方法一次,新增和修改的 之前的逻辑写到这里, 如通用的检查, 计算值得处理
  6. */
  7. public void test3() {
  8. //获取一个 带前缀TL 带日期字符(yyMMdd) + 6位数递增的序列号 的流水号
  9. logger.debug(
  10. keyBuild()
  11. .setPrefix("TL") //设置前缀
  12. .setSuffix("END") //设置后缀
  13. .setDateFormat("yyyyMMdd") //设置日期格式
  14. .getKey("id",3) //参数依次是 1.字段名 2.序列号长度
  15. );
  16. }
  17. }
  • 假如生成的流水号 是 TL20190601001END , 其中 TL 是前缀, 20190601是年月日, 001是递增的序列号, END 是后缀
  • 日期格式可以自定, 例如: yyyyMMdd yyMM MMdd yyMMdd yMMDD

3.3. 批量流水号

一次要获得多个流水号, 如企业内部系统 的 订单导入等, 建议用如下办法获得一批流水号

  1. package com.test.service;
  2. @Service
  3. public class KeyBuildService extends AdvancedService<UserDao, UserDTO> {
  4. public void test4() {
  5. logger.debug("获得多个流水号");
  6. //获得多个序列号
  7. ConcurrentLinkedQueue<String> queue =
  8. keyBuild("TL", "yyMMdd") //前缀, 设置日期格式
  9. .getKeys("id", 6, 4); //参数依次是 1.字段名 2.序列号长度 3.要获得流水号个数
  10. // poll 执行一次, 容器 queue里面少一个
  11. logger.debug(queue.poll());//获得第1个序列号
  12. logger.debug(queue.poll());//获得第2个序列号
  13. logger.debug(queue.poll());//获得第3个序列号
  14. logger.debug(queue.poll());//获得第4个序列号
  15. }
  16. }

也可以写成

  1. package com.test.service;
  2. @Service
  3. public class KeyBuildService extends AdvancedService<UserDao, UserDTO> {
  4. public void test5() {
  5. logger.debug("获得多个流水号");
  6. //获得多个序列号
  7. ConcurrentLinkedQueue<String> queue =
  8. keyBuild()
  9. .setPrefix("TL") //设置前缀
  10. .setDateFormat("yyyyMMdd") //设置日期格式
  11. .getKeys("id", 6, 4); //参数依次是 1.字段名 2.序列号长度 3.要获得流水号个数
  12. ...
  13. }
  14. }

4. 多Service时务必正取获取流水号

下面的例子 当前Service 是 Order 订单表, 注入了 用户的UserService;

  1. package com.user.service;
  2. @Service
  3. public class DeptService extends AdvancedService<DeptDao, DeptDTO> {
  4. @Autowired
  5. UserService userService;
  6. public void test5() {
  7. UserDTO user = new UserDTO();
  8. user.setId(
  9. //获得一个用户的流水号 (错误的代码)---其实获取到的是 Dept表的流水号
  10. keyBuild().getKey("id",3)
  11. );
  12. user.setName("张三");
  13. //...
  14. userService.insert(user);
  15. }
  16. }

假如用户表中只有2条数据,用户id 字段的值分别为 001和002 ; 订单表中只有3条数据, 订单id 字段的值分别为 110,111和112. 上面的程序执行后插入到数据库记录的用户Id是113, 而不是正确的003.

  • 获得用户流水号正确的程序代码是:
  1. user.setId(
  2. userService.keyBuild().getKey("id",3) //纠正后的代码
  3. );