自动生成主键ID的原理及实现方法

在关系型数据库中,主键是一种用于唯一标识表中每条记录的字段。主键的选择对于数据库的性能和数据完整性非常重要。在Java中,我们常常需要为实体类生成主键ID。本文将介绍主键ID的生成原理以及几种常用的实现方法。

1. 自动生成主键ID的原理

自动生成主键ID的原理是通过某种算法或机制来生成一个唯一的标识符,以确保每个实体对象都具有不同的主键ID。主键ID的生成通常有以下几个要求:

  1. 唯一性:每个主键ID必须是全局唯一的,不会重复。
  2. 无序性:主键ID的生成不能依赖于上一条记录的ID,不会出现排序问题。
  3. 可读性:主键ID最好是一个可读的字符串,方便人工查看。

2. 常用的主键ID生成方法

2.1 自增长主键

自增长主键是最常见的主键生成方法,主要通过数据库的自增长机制来实现。当插入一条新记录时,数据库会自动为该记录生成一个唯一的主键ID。在Java中,我们可以使用AUTO_INCREMENT关键字来定义自增长主键。例如:

CREATE TABLE `student` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(50) NOT NULL,
  `age` INT(11) NOT NULL,
  PRIMARY KEY (`id`)
)

2.2 UUID主键

UUID(Universally Unique Identifier)是一种由128位数字组成的标识符,它几乎可以保证全球范围内的唯一性。在Java中,我们可以使用java.util.UUID类来生成UUID主键。例如:

import java.util.UUID;

public class Student {
    private String id;
    private String name;
    private int age;

    public Student(String name, int age) {
        this.id = UUID.randomUUID().toString();
        this.name = name;
        this.age = age;
    }

    // getters and setters
}

2.3 Snowflake主键

Snowflake是Twitter开源的分布式ID生成算法,通过在分布式系统中使用不同的机器、时间戳和序列号来生成唯一的ID。Snowflake主键由以下三个部分组成:

  1. 时间戳:占用41位,精确到毫秒级别,可以使用69年。
  2. 机器ID:占用10位,可以部署1024台机器。
  3. 序列号:占用12位,可以每毫秒生成4096个ID。

在Java中,我们可以使用Snowflake算法的实现来生成Snowflake主键。例如:

public class SnowflakeIdGenerator {
    private final long START_TIME = 1569703200000L; // 2019-09-29 00:00:00
    private final long WORKER_ID_BITS = 10L;
    private final long SEQUENCE_BITS = 12L;
    private final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
    private final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private final long WORKER_ID;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format("Worker ID can't be greater than %d or less than 0", MAX_WORKER_ID));
        }
        this.WORKER_ID = workerId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards");
        }

        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - START_TIME) << TIMESTAMP_SHIFT) | (WORKER_ID << WORKER_ID_SHIFT) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();