MyBatis使用雪花ID的实现

作者:陈琰AC 时间:2023-06-09 16:26:23 

一、实现MyBatis ID构建接口

@Slf4j
@Component
public class CustomIdGenerator implements IdentifierGenerator {

@Override
   public Long nextId(Object entity) {
       //生成ID
       long id = SnowFlakeUtils.nextId();
       log.info("生成ID: " + id);
       return id;
   }
}

二、雪花ID生成工具类

@Slf4j
public class SnowFlakeUtils {

/** 初始偏移时间戳 */
   private static final long OFFSET = 1546300800L;

/** 机器id (0~15 保留 16~31作为备份机器) */
   private static final long WORKER_ID;
   /** 机器id所占位数 (5bit, 支持最大机器数 2^5 = 32)*/
   private static final long WORKER_ID_BITS = 5L;
   /** 自增序列所占位数 (16bit, 支持最大每秒生成 2^16 = 65536) */
   private static final long SEQUENCE_ID_BITS = 16L;
   /** 机器id偏移位数 */
   private static final long WORKER_SHIFT_BITS = SEQUENCE_ID_BITS;
   /** 自增序列偏移位数 */
   private static final long OFFSET_SHIFT_BITS = SEQUENCE_ID_BITS + WORKER_ID_BITS;
   /** 机器标识最大值 (2^5 / 2 - 1 = 15) */
   private static final long WORKER_ID_MAX = ((1 << WORKER_ID_BITS) - 1) >> 1;
   /** 备份机器ID开始位置 (2^5 / 2 = 16) */
   private static final long BACK_WORKER_ID_BEGIN = (1 << WORKER_ID_BITS) >> 1;
   /** 自增序列最大值 (2^16 - 1 = 65535) */
   private static final long SEQUENCE_MAX = (1 << SEQUENCE_ID_BITS) - 1;
   /** 发生时间回拨时容忍的最大回拨时间 (秒) */
   private static final long BACK_TIME_MAX = 1000L;

/** 上次生成ID的时间戳 (秒) */
   private static long lastTimestamp = 0L;
   /** 当前秒内序列 (2^16)*/
   private static long sequence = 0L;
   /** 备份机器上次生成ID的时间戳 (秒) */
   private static long lastTimestampBak = 0L;
   /** 备份机器当前秒内序列 (2^16)*/
   private static long sequenceBak = 0L;

static {
       // 初始化机器ID
       long workerId = getWorkId();
       if (workerId < 0 || workerId > WORKER_ID_MAX) {
           throw new IllegalArgumentException(String.format("cmallshop.workerId范围: 0 ~ %d 目前: %d", WORKER_ID_MAX, workerId));
       }
       WORKER_ID = workerId;
   }

private static Long getWorkId(){
       try {
           String hostAddress = Inet4Address.getLocalHost().getHostAddress();
           int[] ints = StringUtils.toCodePoints(hostAddress);
           int sums = 0;
           for(int b : ints){
               sums += b;
           }
           return (long)(sums % WORKER_ID_MAX);
       } catch (UnknownHostException e) {
           // 如果获取失败,则使用随机数备用
           return RandomUtils.nextLong(0,WORKER_ID_MAX-1);
       }
   }

/** 私有构造函数禁止外部访问 */
   private SnowFlakeUtils() {}

/**
    * 获取自增序列
    * @return long
    */
   public static long nextId() {
       return nextId(SystemClock.now() / 1000);
   }

/**
    * 主机器自增序列
    * @param timestamp 当前Unix时间戳
    * @return long
    */
   private static synchronized long nextId(long timestamp) {
       // 时钟回拨检查
       if (timestamp < lastTimestamp) {
           // 发生时钟回拨
           log.warn("时钟回拨, 启用备份机器ID: now: [{}] last: [{}]", timestamp, lastTimestamp);
           return nextIdBackup(timestamp);
       }

// 开始下一秒
       if (timestamp != lastTimestamp) {
           lastTimestamp = timestamp;
           sequence = 0L;
       }
       if (0L == (++sequence & SEQUENCE_MAX)) {
           // 秒内序列用尽
//            log.warn("秒内[{}]序列用尽, 启用备份机器ID序列", timestamp);
           sequence--;
           return nextIdBackup(timestamp);
       }

return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | (WORKER_ID << WORKER_SHIFT_BITS) | sequence;
   }

/**
    * 备份机器自增序列
    * @param timestamp timestamp 当前Unix时间戳
    * @return long
    */
   private static long nextIdBackup(long timestamp) {
       if (timestamp < lastTimestampBak) {
           if (lastTimestampBak - SystemClock.now() / 1000 <= BACK_TIME_MAX) {
               timestamp = lastTimestampBak;
           } else {
               throw new RuntimeException(String.format("时钟回拨: now: [%d] last: [%d]", timestamp, lastTimestampBak));
           }
       }

if (timestamp != lastTimestampBak) {
           lastTimestampBak = timestamp;
           sequenceBak = 0L;
       }

if (0L == (++sequenceBak & SEQUENCE_MAX)) {
           // 秒内序列用尽
//            logger.warn("秒内[{}]序列用尽, 备份机器ID借取下一秒序列", timestamp);
           return nextIdBackup(timestamp + 1);
       }

return ((timestamp - OFFSET) << OFFSET_SHIFT_BITS) | ((WORKER_ID ^ BACK_WORKER_ID_BEGIN) << WORKER_SHIFT_BITS) | sequenceBak;
   }

/**
    * 并发数
    */
   private static final int THREAD_NUM = 30000;
   private static volatile CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);

public static void main(String[] args) {
       ConcurrentHashMap<Long,Long> map = new ConcurrentHashMap<>(THREAD_NUM);
       List<Long> list = Collections.synchronizedList(new LinkedList<>());

for (int i = 0; i < THREAD_NUM; i++) {
           Thread thread = new Thread(() -> {
               // 所有的线程在这里等待
               try {
                   countDownLatch.await();

Long id = SnowFlakeUtils.nextId();
                   list.add(id);
                   map.put(id,1L);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           });

thread.start();
           // 启动后,倒计时计数器减一,代表有一个线程准备就绪了
           countDownLatch.countDown();
       }

try{
           Thread.sleep(50000);
       }catch (Exception e){
           e.printStackTrace();
       }

System.out.println("listSize:"+list.size());
       System.out.println("mapSize:"+map.size());
       System.out.println(map.size() == THREAD_NUM);
   }
}

来源:https://www.jianshu.com/p/a17dd394501b

标签:MyBatis,雪花ID
0
投稿

猜你喜欢

  • java 排序算法之快速排序

    2022-07-23 17:39:03
  • Android下拉刷新以及GridView使用方法详解

    2022-12-26 18:01:24
  • C#中WPF颜色对话框控件的实现

    2023-04-13 06:09:41
  • Android实现图片拖拉功能

    2023-03-27 14:57:51
  • 如何用.NETCore操作RabbitMQ

    2022-06-20 04:11:04
  • Spring Boot如何整合FreeMarker模板引擎

    2022-09-06 15:49:32
  • 浅析JAVA中的内存结构、重载、this与继承

    2023-09-24 19:15:07
  • Android实现购物车添加商品特效

    2021-06-25 22:59:45
  • Java毕业设计实战之校园一卡通系统的实现

    2022-11-26 06:32:56
  • java微信公众号开发案例

    2023-01-30 11:05:36
  • Java Validation方法入参校验实现过程解析

    2021-08-04 03:31:50
  • Android模仿微信收藏文件的标签处理功能

    2022-07-17 05:32:18
  • Spring Bean的包扫描的实现方法

    2021-10-21 12:40:07
  • Android 中启动自己另一个程序的activity如何实现

    2022-05-17 05:24:56
  • 基于Fedora14下自带jdk1.6版本 安装jdk1.7不识别的解决方法

    2022-05-17 19:27:39
  • jvm运行原理以及类加载器实例详解

    2023-10-23 18:39:03
  • Java数据结构之链表实现(单向、双向链表及链表反转)

    2021-10-17 18:04:25
  • android点击无效验证的解决方法

    2022-02-28 03:55:08
  • C#使用Ado.net读取Excel表的方法

    2022-04-22 02:01:47
  • 使用JMX连接JVM实现过程详解

    2022-07-25 23:05:45
  • asp之家 软件编程 m.aspxhome.com