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 可以处理 String 和 UUID 两种 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 文本类型,如 TEXT 或 VARCHAR。
5. 总结
本文中,我们学习了使用 Hibernate 生成 UUID 的不同方式。
在 Jakarta 3.1.0 规范和 Hibernate 6.2 之前有几种实现方式。而在 Hibernate 6.2 及以后,我们可以使用新的 JPA GenerationType.UUID
来独立于持久化提供程序生成 UUID。
然而,JPA 规范并未指定生成的 UUID 版本。如果我们想要指定具体的版本,就需要使用 Hibernate 特定的类,并且我们仍然有两个选项——版本1或版本4。
一如既往,本教程的源代码可在 GitHub 上找到。