以下是“java基于数据库实现全局唯一ID的示例”的完整攻略及两条示例:
一、前置条件
在进行本教程之前,请确保以下条件已经满足:
- 你已熟悉Java编程语言,并且能够独立编写Java代码;
- 你已经安装了MySQL数据库,并掌握了基本操作;
- 你已经安装了Java开发环境和相关依赖库。
二、方案选择
目前常见的实现全局唯一ID的方案有雪花算法、UUID等。本教程将以雪花算法为例,介绍基于数据库实现全局唯一ID的方法。
三、实现过程
1.创建数据库
首先,需要在MySQL数据库中创建一个表,用于存储唯一ID的信息。
CREATE TABLE `t_unique_id`
(
`id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'ID' PRIMARY KEY AUTO_INCREMENT,
`gmt_create` DATETIME NOT NULL COMMENT '创建时间',
`data_center_id` INT(11) UNSIGNED NOT NULL COMMENT '数据中心ID',
`machine_id` INT(11) UNSIGNED NOT NULL COMMENT '机器ID',
`sequence` INT(11) UNSIGNED NOT NULL COMMENT '序列号',
UNIQUE KEY `uk_data_center_id_machine_id_sequence` (`data_center_id`, `machine_id`, `sequence`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='唯一ID生成记录表';
说明:
id
:主键,唯一标识符。gmt_create
:记录创建时间。data_center_id
:数据中心ID,用于标识数据中心(最多支持32个数据中心,每个数据中心最多支持1024台机器)。machine_id
:机器ID,用于标识某一台机器。sequence
:序列号,用于标识某一毫秒内生成的ID序列。
其中,data_center_id
、machine_id
、sequence
三列加上唯一索引,用于保证同一时刻生成的ID不重复。
2.编写Java代码
import java.sql.*;
import java.util.concurrent.atomic.AtomicLong;
public class UniqueIdGenerator {
// 数据中心ID
private final long dataCenterId;
// 机器ID
private final long machineId;
// 序列号
private final AtomicLong sequence = new AtomicLong(0L);
// 构造函数
public UniqueIdGenerator(long dataCenterId, long machineId) {
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
// 获取唯一ID
public long nextId() throws SQLException {
while (true) {
try (Connection connection = getConnection()) {
// 获取当前时间戳
long timestamp = System.currentTimeMillis();
// 从数据库中查询记录
PreparedStatement statement = connection.prepareStatement("SELECT sequence FROM t_unique_id WHERE data_center_id = ? AND machine_id = ?");
statement.setLong(1, dataCenterId);
statement.setLong(2, machineId);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
// 如果数据库中已经存在记录,则更新记录的序列号
long sequence = resultSet.getLong(1);
if (isValid(timestamp, sequence)) {
update(sequence + 1);
return ((timestamp - 1400000000000L) << 22) | (dataCenterId << 17) | (machineId << 12) | (sequence + 1);
}
} else {
// 如果数据库中不存在记录,则插入一条新的记录,并返回唯一ID
boolean inserted = insert();
if (inserted) {
return ((timestamp - 1400000000000L) << 22) | (dataCenterId << 17) | (machineId << 12) | 1;
}
}
}
// 等待下一毫秒
try {
Thread.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 更新记录的序列号
private void update(long sequence) throws SQLException {
try (Connection connection = getConnection()) {
PreparedStatement statement = connection.prepareStatement("UPDATE t_unique_id SET sequence = ? WHERE data_center_id = ? AND machine_id = ?");
statement.setLong(1, sequence);
statement.setLong(2, dataCenterId);
statement.setLong(3, machineId);
statement.executeUpdate();
}
}
// 插入新的记录
private boolean insert() throws SQLException {
try (Connection connection = getConnection()) {
PreparedStatement statement = connection.prepareStatement("INSERT INTO t_unique_id (gmt_create, data_center_id, machine_id, sequence) VALUES (?, ?, ?, ?)");
statement.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
statement.setLong(2, dataCenterId);
statement.setLong(3, machineId);
statement.setLong(4, 1L);
return statement.executeUpdate() > 0;
}
}
// 获取数据库连接
private Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8";
String username = "root";
String password = "password";
return DriverManager.getConnection(url, username, password);
}
// 判断是否有效
private boolean isValid(long timestamp, long sequence) {
long currentTimestamp = System.currentTimeMillis();
return timestamp - currentTimestamp <= 1L && sequence <= 1023L;
}
}
说明:
dataCenterId
、machindId
、sequence
分别表示数据中心ID、机器ID和序列号,其中sequence
使用AtomicLong
实现线程安全递增。nextId
方法用于获取一个唯一ID,采用了数据库交互方式实现。首先从数据库中查询是否有记录,如果有则更新记录的序列号;如果没有则插入一条新的记录,并返回唯一ID。注意:需要在一个毫秒内保证生成的唯一ID是不重复的。update
方法用于更新记录的序列号。insert
方法用于插入新的记录。getConnection
方法用于获取数据库连接。isValid
方法用于判断记录是否有效,保证同一毫秒内生成的ID不重复。
3.测试
以下是两条测试代码:
示例 1:单线程测试
public class UniqueIdGeneratorTest {
@Test
public void test() throws SQLException {
UniqueIdGenerator generator = new UniqueIdGenerator(1L, 1L);
long id = generator.nextId();
System.out.println(id);
Assert.assertNotEquals(0L, id);
}
}
说明:
- 构造函数中的参数
1L
和1L
分别表示数据中心ID和机器ID。 - 测试代码只是简单调用,并输出ID。
示例 2:多线程测试
public class UniqueIdGeneratorTest {
@Test
public void test() throws SQLException, InterruptedException, ExecutionException {
int threadCount = 100;
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
List<Future<Long>> futures = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
Future<Long> future = executorService.submit(() -> {
UniqueIdGenerator generator = new UniqueIdGenerator(1L, 1L);
return generator.nextId();
});
futures.add(future);
}
Set<Long> ids = new HashSet<>();
for (Future<Long> future : futures) {
long id = future.get();
System.out.println(id);
Assert.assertNotEquals(0L, id);
Assert.assertFalse(ids.contains(id));
ids.add(id);
}
}
}
说明:
threadCount
表示测试的线程数。- 使用线程池同时生成多个唯一ID,并将生成的ID保存到
HashSet
中,用于判断是否出现重复ID。
四、总结
通过本教程,我们实现了基于数据库的全局唯一ID,具有以下特点:
- 算法简单,实现成本低;
- 毫秒内保证ID的唯一性,效率高;
- 支持多个数据中心和多台机器,灵活性强。
该方案可以广泛应用于分布式系统中,例如订单号、流水号等业务场景。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java基于数据库实现全局唯一ID的示例 - Python技术站