自动生成主键ID的原理及实现方法
在关系型数据库中,主键是一种用于唯一标识表中每条记录的字段。主键的选择对于数据库的性能和数据完整性非常重要。在Java中,我们常常需要为实体类生成主键ID。本文将介绍主键ID的生成原理以及几种常用的实现方法。
1. 自动生成主键ID的原理
自动生成主键ID的原理是通过某种算法或机制来生成一个唯一的标识符,以确保每个实体对象都具有不同的主键ID。主键ID的生成通常有以下几个要求:
- 唯一性:每个主键ID必须是全局唯一的,不会重复。
- 无序性:主键ID的生成不能依赖于上一条记录的ID,不会出现排序问题。
- 可读性:主键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主键由以下三个部分组成:
- 时间戳:占用41位,精确到毫秒级别,可以使用69年。
- 机器ID:占用10位,可以部署1024台机器。
- 序列号:占用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();