java基于数据库实现全局唯一ID的示例

以下是“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_idmachine_idsequence三列加上唯一索引,用于保证同一时刻生成的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;
    }

}

说明:

  • dataCenterIdmachindIdsequence分别表示数据中心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);
    }

}

说明:

  • 构造函数中的参数1L1L分别表示数据中心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技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • spring-boot-plus V1.4.0发布 集成用户角色权限部门管理(推荐)

    Spring Boot Plus V1.4.0发布 Spring Boot Plus是一个基于SpringBoot的项目快速开发脚手架,版本 V1.4.0 提供了用户角色权限部门管理的集成,方便用户快速搭建管理后台。 安装 首先,我们需要安装Java和Maven,参考:- Java 安装教程- Maven 安装教程 Spring Boot Plus 是通过M…

    Java 2023年5月20日
    00
  • Java 数组转List的四种方式小结

    Java 数组转 List 的四种方式小结 在 Java 开发中,数组和 List 是非常常见的数据类型。有时我们需要将数组转换成 List 以便进行操作。本文将介绍四种将 Java 数组转换成 List 的常用方法。 方法一:使用 Arrays.asList() Arrays 类提供了一个 asList() 方法,可以将数组转换成 List。 SomeTy…

    Java 2023年5月26日
    00
  • @RequestBody注解Ajax post json List集合数据请求400/415的处理

    首先介绍一下问题的背景,前端使用Ajax技术向后端发送一个POST请求,请求的数据是JSON格式的List集合数据,后端使用Spring MVC框架,利用注解@RequestBody将这个JSON数据映射到后端的Java对象中。但是在处理过程中,出现了400或者415的错误码,这是因为后端无法正确解析请求的JSON数据。那么如何处理这个问题呢?接下来我们来一…

    Java 2023年5月26日
    00
  • Java Scala数据类型与变量常量及类和对象超详细讲解

    Java Scala数据类型与变量常量及类和对象超详细讲解 一、Java Scala数据类型 在Java Scala中,数据类型主要分为以下几种: 基本数据类型:包括整型、浮点型、布尔型和字符型等。 数组类型:包括一维数组和多维数组。 引用数据类型:包括类类型、接口类型、枚举类型和数组类型等。 下面我们分别对每种数据类型进行详细讲解: 1.1 基本数据类型 …

    Java 2023年5月26日
    00
  • 如何通过LambdaProbe实现监控Tomcat

    LambdaProbe是一种轻量级的Tomcat管理和监控工具,可以帮助我们更方便地查看Tomcat运行状态、性能指标和日志等信息。下面是通过LambdaProbe实现监控Tomcat的完整攻略,包含以下内容: 下载和安装LambdaProbe 配置Tomcat 启动Tomcat和LambdaProbe 使用LambdaProbe监控Tomcat 下载和安装…

    Java 2023年6月2日
    00
  • 详谈hibernate,jpa与spring data jpa三者之间的关系

    详谈hibernate,jpa与spring data jpa三者之间的关系 什么是Hibernate? Hibernate是一个开源的ORM(Object Relational Mapping)框架,旨在通过映射Java对象和数据库表,将数据持久化到数据库中。Hibernate执行了许多数据库操作并自动处理所有底层细节,使得编写数据访问层的代码变得更简单。…

    Java 2023年6月3日
    00
  • 如何解决struts2日期类型转换

    解决struts2日期类型转换问题的完整攻略如下: 问题描述 在使用struts2框架中,如果后台 Action 接收的参数是日期类型,容易出现类型转换异常。例如,在前端页面中,日期类型通常采用字符串格式传递,如“2019-10-01”,但是在后台 Action 中,需要将该字符串转换为日期类型对象,否则无法正确处理业务逻辑。如果日期格式不一致,将会出现类型…

    Java 2023年6月2日
    00
  • jQuery Ajax传值到Servlet出现乱码问题的解决方法

    下面是详细的攻略: 问题背景 在使用 jQuery Ajax 技术将数据传递到 Servlet 后台时,有时会遇到中文乱码的问题,这是因为在传输过程中,字符编码格式不统一,导致原本正确的中文字符被解析成乱码的字符。 解决方法 为了解决这个问题,我们需要对字符编码格式进行统一,可以通过以下两种方法实现。 解决方法一:手动设置字符编码格式 在 jQuery Aj…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部