1. 引言

UUID 是数据库中较为常见的一种主键类型。它几乎全球唯一,这使得它成为分布式系统中ID类型的理想选择。

在本教程中,我们将探讨如何利用 Hibernate 和 JPA 为我们的实体生成 UUID。

2. JPA/Jakarta 规范

首先,我们来看一下 JPA 提供了哪些功能来解决这一问题。

自 2022 年发布的 3.1.0 版本起,JPA 规范为开发者提供了一个新的 GenerationType.UUID,我们可以在 @GeneratedValue 注解中使用它:

@Entity
class Reservation {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    private String status;

    private String number;

    // getters and setters
}

GenerationType 指示实体的 UUID 应由持久化提供程序自动为我们生成。

特别是 Hibernate,从 6.2 版本开始支持 JPA 3.1.0【参考链接】(https://hibernate.org/orm/releases/6.2/)。因此,只要使用至少 Hibernate 6.2,以下代码将生效:

@Test
public void whenGeneratingUUIDUsingNewJPAGenerationType_thenHibernateGeneratedUUID() throws IOException {
    Reservation reservation = new Reservation();
    reservation.setStatus("created");
    reservation.setNumber("12345");
    UUID saved = (UUID) session.save(reservation);
    Assertions.assertThat(saved).isNotNull();
}

然而,在RFC 4122中定义了四种类型的 UUID。而 JPA 规范将 UUID 版本的选择留给了持久化提供程序。因此,不同的持久化提供程序可能会生成不同版本的 UUID

默认情况下,Hibernate 生成第四版的 UUID:

@Test
public void whenGeneratingUUIDUsingNewJPAGenerationType_thenHibernateGeneratedUUIDOfVersion4() throws IOException {
    Reservation reservation = new Reservation();
    reservation.setStatus("new");
    reservation.setNumber("012");
    UUID saved = (UUID) session.save(reservation);
    Assertions.assertThat(saved).isNotNull();
    Assertions.assertThat(saved.version()).isEqualTo(4);
}

根据 RFC 4122,Hibernate 能够创建两个版本(1和4)的 UUID。稍后我们将看到如何生成基于时间(版本1)的 UUID。

3. 在 Hibernate 6.2 之前

在某些项目中,直接从 JPA 2.x 规范跳到 JPA(或 Jakarta)3.1.0 规范可能不可行。但是,如果我们使用的是 Hibernate 4 或 5,我们仍然有能力生成 UUID。为此,我们有两种方法。

首先,我们可以通过在 @GenericGenerator 注解中指定 org.hibernate.id.UUIDGenerator 类来实现这一点:

@Entity
class Sale {

    @Id
    @GeneratedValue(generator = "uuid-hibernate-generator")
    @GenericGenerator(name = "uuid-hibernate-generator", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID id;

    private boolean completed;

    //getters and setters
}

其行为将与 Hibernate 6.2 相同:

@Test
public void whenGeneratingUUIDUsingGenericConverter_thenAlsoGetUUIDGeneratedVersion4() throws IOException {
    Sale sale = new Sale();
    sale.setCompleted(true);
    UUID saved = (UUID) session.save(sale);
    Assertions.assertThat(saved).isNotNull();
    Assertions.assertThat(saved.version()).isEqualTo(4);
}

然而,这种方法相当冗长,我们只需使用 org.hibernate.annotations.UuidGenerator 注解就可以获得相同的行为:

@Entity
class Sale {

    @Id
    @UuidGenerator
    private UUID id;

    private boolean completed;

    // getters and setters 
}

此外,当指定 @UuidGenerator 时,我们可以选择要生成的 UUID 具体版本。这由 style 参数定义。让我们看看这个参数可以取哪些值:

  • RANDOM – 基于随机数生成 UUID(RFC 中的版本4)
  • TIME – 生成基于时间的 UUID(RFC 中的版本1)
  • AUTO – 这是默认选项,等同于 RANDOM

接下来,我们看看如何控制 Hibernate 生成的 UUID 版本:

@Entity
class WebSiteUser {

    @Id
    @UuidGenerator(style = UuidGenerator.Style.TIME)
    private UUID id;

    private LocalDate registrationDate;

    // getters and setters
}

现在,我们可以检查到 Hibernate 将会生成基于时间(版本1)的 UUID:

@Test
public void whenGeneratingTimeBasedUUID_thenUUIDGeneratedVersion1() throws IOException {
    WebSiteUser user = new WebSiteUser();
    user.setRegistrationDate(LocalDate.now());
    UUID saved = (UUID) session.save(user);
    Assertions.assertThat(saved).isNotNull();
    Assertions.assertThat(saved.version()).isEqualTo(1);
}

4. 使用 String 作为 UUID

另外,如果我们将 String 用作 Java ID 类型,Hibernate 也足够智能以生成 UUID:

@Entity
class Element {

    @Id
    @UuidGenerator
    private String id;

    private String name;
}

如我们所见,Hibernate 可以处理 StringUUID 两种 Java 类型:

@Test
public void whenGeneratingUUIDAsString_thenUUIDGeneratedVersion1() throws IOException {
    Element element = new Element();
    element.setName("a");
    String saved = (String) session.save(element);
    Assertions.assertThat(saved).isNotEmpty();
    Assertions.assertThat(UUID.fromString(saved).version()).isEqualTo(4);
}

这里需要注意的是,当我们设置一个列的类型为 java.util.UUID 时,Hibernate 试图将其映射到数据库中的相应 UUID 类型。这个类型因数据库而异

因此,确切的类型实际上取决于设置的 Hibernate 方言。例如,如果我们使用 PostgreSQL,那么对应的类型将是 PostgreSQL 中的 UUID。如果我们使用 Microsoft SQL Server,则对应的类型将是 UNIQUEIDENTIFIER。然而,如果我们使用 String 作为 Java ID 类型,那么 Hibernate 将其映射到某种 SQL 文本类型,如 TEXTVARCHAR

5. 总结

本文中,我们学习了使用 Hibernate 生成 UUID 的不同方式。

在 Jakarta 3.1.0 规范和 Hibernate 6.2 之前有几种实现方式。而在 Hibernate 6.2 及以后,我们可以使用新的 JPA GenerationType.UUID 来独立于持久化提供程序生成 UUID。

然而,JPA 规范并未指定生成的 UUID 版本。如果我们想要指定具体的版本,就需要使用 Hibernate 特定的类,并且我们仍然有两个选项——版本1或版本4。

一如既往,本教程的源代码可在 GitHub 上找到