• Hibernate/JPA 中的id概述


    1. 概述

    Hibernate中的标识符表示实体的主键。这意味着这些值是唯一的,因此它们可以标识特定实体,它们不为 null,也不会被修改。

    Hibernate提供了几种不同的方法来定义标识符。在本文中,我们将回顾使用该库映射实体 ID 的每种方法。

    2. 简单标识符

    定义标识符的最直接方法是使用@Id注释。

    简单 id 使用以下@Id映射到以下类型之一的单个属性:Java 原始和原始包装器类型、字符串日期BigDecimal 和 BigInteger

    让我们看一个定义主键类型为long 的实体的快速示例:

    1. @Entity
    2. public class Student {
    3. @Id
    4. private long studentId;
    5. // standard constructor, getters, setters
    6. }
    Copy

    3. 生成的标识符

    如果我们想自动生成主键值,我们可以添加@GeneratedValue注释。

    这可以使用四种生成类型:自动、标识、序列和表。

    如果我们没有显式指定值,则生成类型默认为 AUTO。

    3.1.自动生成

    如果我们使用默认生成类型,持久性提供程序将根据主键属性的类型确定值。此类型可以是数字或UUID。

    对于数值,生成基于序列或表生成器,而UUID值将使用UUIDGenerator

    让我们首先使用 AUTO 生成策略映射实体主键:

    1. @Entity
    2. public class Student {
    3. @Id
    4. @GeneratedValue
    5. private long studentId;
    6. // ...
    7. }
    Copy

    在这种情况下,主键值在数据库级别是唯一的。

    现在我们来看看在Hibernate 5中引入的UUIDGenerator

    为了使用此功能,我们只需要声明一个UUID类型的 id,带有@GeneratedValue注释:

    1. @Entity
    2. public class Course {
    3. @Id
    4. @GeneratedValue
    5. private UUID courseId;
    6. // ...
    7. }
    Copy

    Hibernate将生成一个形式的id“8dd5f315-9788-4d00-87bb-10eed9eff566”。

    3.2.身份生成

    这种类型的生成依赖于IdentityGenerator,它需要由数据库中的标识列生成的值。这意味着它们是自动递增的。

    要使用这种生成类型,我们只需要设置策略参数:

    1. @Entity
    2. public class Student {
    3. @Id
    4. @GeneratedValue (strategy = GenerationType.IDENTITY)
    5. private long studentId;
    6. // ...
    7. }
    Copy

    需要注意的一点是,标识生成会禁用批处理更新。

    3.3.序列生成

    为了使用基于序列的 id,Hibernate 提供了SequenceStyleGenerator类。

    如果我们的数据库支持序列,则此生成器使用序列。如果不支持表生成,它将切换到表生成。

    为了自定义序列名称,我们可以使用带有SequenceStyleGenerator 策略@GenericGenerator注释:

    1. @Entity
    2. public class User {
    3. @Id
    4. @GeneratedValue(generator = "sequence-generator")
    5. @GenericGenerator(
    6. name = "sequence-generator",
    7. strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
    8. parameters = {
    9. @Parameter(name = "sequence_name", value = "user_sequence"),
    10. @Parameter(name = "initial_value", value = "4"),
    11. @Parameter(name = "increment_size", value = "1")
    12. }
    13. )
    14. private long userId;
    15. // ...
    16. }
    Copy

    在此示例中,我们还为序列设置了一个初始值,这意味着主键生成将从 4 开始。

    SEQUENCE是 Hibernate 文档推荐的生成类型。

    生成的值对于每个序列都是唯一的。如果我们不指定序列名称,Hibernate将对不同的类型重用相同的hibernate_sequence

    3.4. 表生成

    生成器使用保存标识符生成值段的基础数据库表。

    让我们使用@TableGenerator注释自定义表名:

    1. @Entity
    2. public class Department {
    3. @Id
    4. @GeneratedValue(strategy = GenerationType.TABLE,
    5. generator = "table-generator")
    6. @TableGenerator(name = "table-generator",
    7. table = "dep_ids",
    8. pkColumnName = "seq_id",
    9. valueColumnName = "seq_value")
    10. private long depId;
    11. // ...
    12. }
    Copy

    在这个例子中,我们可以看到我们还可以自定义其他属性,例如pkColumnName 和 valueColumnName

    但是,此方法的缺点是它不能很好地扩展,并且可能会对性能产生负面影响。

    总而言之,这四种生成类型将导致生成相似的值,但使用不同的数据库机制。

    3.5. 自定义生成器

    假设我们不想使用任何开箱即用的策略。为了做到这一点,我们可以通过实现IdentifierGenerator接口来定义我们的自定义生成器。

    我们将创建一个生成器,用于构建包含字符串前缀和数字的标识符:

    1. public class MyGenerator
    2. implements IdentifierGenerator, Configurable {
    3. private String prefix;
    4. @Override
    5. public Serializable generate(
    6. SharedSessionContractImplementor session, Object obj)
    7. throws HibernateException {
    8. String query = String.format("select %s from %s",
    9. session.getEntityPersister(obj.getClass().getName(), obj)
    10. .getIdentifierPropertyName(),
    11. obj.getClass().getSimpleName());
    12. Stream ids = session.createQuery(query).stream();
    13. Long max = ids.map(o -> o.replace(prefix + "-", ""))
    14. .mapToLong(Long::parseLong)
    15. .max()
    16. .orElse(0L);
    17. return prefix + "-" + (max + 1);
    18. }
    19. @Override
    20. public void configure(Type type, Properties properties,
    21. ServiceRegistry serviceRegistry) throws MappingException {
    22. prefix = properties.getProperty("prefix");
    23. }
    24. }
    Copy

    在这个例子中,我们从IdentifierGenerator接口覆盖generate() 方法。

    首先,我们要从形式前缀 XX 的现有主键中找到最大数字。然后,我们将找到的最大数量加 1,并附加前缀属性以获取新生成的 id 值。

    我们的类还实现了可配置接口,以便我们可以在configure() 方法中设置前缀属性值。

    接下来,让我们将此自定义生成器添加到实体。

    为此,我们可以将 @GenericGenerator注释与包含生成器类的完整类名的策略参数一起使用

    1. @Entity
    2. public class Product {
    3. @Id
    4. @GeneratedValue(generator = "prod-generator")
    5. @GenericGenerator(name = "prod-generator",
    6. parameters = @Parameter(name = "prefix", value = "prod"),
    7. strategy = "com.baeldung.hibernate.pojo.generator.MyGenerator")
    8. private String prodId;
    9. // ...
    10. }
    Copy

    另外,请注意,我们已将前缀参数设置为“prod”。

    让我们看一个快速的 JUnit 测试,以便更清楚地了解生成的 id 值:

    1. @Test
    2. public void whenSaveCustomGeneratedId_thenOk() {
    3. Product product = new Product();
    4. session.save(product);
    5. Product product2 = new Product();
    6. session.save(product2);
    7. assertThat(product2.getProdId()).isEqualTo("prod-2");
    8. }
    Copy

    此处,使用 “prod” 前缀生成的第一个值是 “prod-1”,后跟 “prod-2”。

    4. 复合标识符

    除了我们目前看到的简单标识符之外,Hibernate还允许我们定义复合标识符。

    复合 ID 由具有一个或多个持久属性的主键类表示。

    主键类必须满足以下几个条件

    • 它应该使用@EmbeddedId@IdClass注释来定义。
    • 它应该是公共的、可序列化的,并且有一个公共的无参数构造函数。
    • 最后,它应该实现equals() 和hashCode() 方法。

    类的属性可以是基本、复合或多对一,同时避免集合和一对一属性。

    4.1. @EmbeddedId

    现在让我们看看如何使用@EmbeddedId定义 id。

    首先,我们需要一个主键类,注释如下@Embeddable

    1. @Embeddable
    2. public class OrderEntryPK implements Serializable {
    3. private long orderId;
    4. private long productId;
    5. // standard constructor, getters, setters
    6. // equals() and hashCode()
    7. }
    Copy

    接下来,我们可以使用 @EmbeddedId 将OrderEntryPK类型的 id 添加到实体:

    1. @Entity
    2. public class OrderEntry {
    3. @EmbeddedId
    4. private OrderEntryPK entryId;
    5. // ...
    6. }
    Copy

    让我们看看如何使用这种类型的复合 id 来设置实体的主键:

    1. @Test
    2. public void whenSaveCompositeIdEntity_thenOk() {
    3. OrderEntryPK entryPK = new OrderEntryPK();
    4. entryPK.setOrderId(1L);
    5. entryPK.setProductId(30L);
    6. OrderEntry entry = new OrderEntry();
    7. entry.setEntryId(entryPK);
    8. session.save(entry);
    9. assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
    10. }
    Copy

    在这里,OrderEntry对象有一个由两个属性组成的OrderEntryPK主 ID:orderIdproductId

    4.2. @IdClass

    @IdClass注释类似于@EmbeddedId。与@IdClass的区别在于,属性是在主实体类中使用@Id为每个属性定义的。主键类看起来与以前相同。

    让我们用 @IdClass 重写OrderEntry示例:

    1. @Entity
    2. @IdClass(OrderEntryPK.class)
    3. public class OrderEntry {
    4. @Id
    5. private long orderId;
    6. @Id
    7. private long productId;
    8. // ...
    9. }
    Copy

    然后我们可以直接在OrderEntry对象上设置 id 值:

    1. @Test
    2. public void whenSaveIdClassEntity_thenOk() {
    3. OrderEntry entry = new OrderEntry();
    4. entry.setOrderId(1L);
    5. entry.setProductId(30L);
    6. session.save(entry);
    7. assertThat(entry.getOrderId()).isEqualTo(1L);
    8. }
    Copy

    请注意,对于这两种类型的复合 ID,主键类还可以包含@ManyToOne属性。

    Hibernate还允许定义由@ManyToOne关联与@Id注释组成的主键。在这种情况下,实体类还应满足主键类的条件。

    但是,此方法的缺点是实体对象和标识符之间没有分离。

    5. 派生标识符

    派生标识符是使用 @MapsId注释从实体的关联中获取的。

    首先,让我们创建一个UserProfile实体,该实体从与User实体的一对一关联中派生其 id:

    1. @Entity
    2. public class UserProfile {
    3. @Id
    4. private long profileId;
    5. @OneToOne
    6. @MapsId
    7. private User user;
    8. // ...
    9. }
    Copy

    接下来,让我们验证用户配置文件实例是否与其关联的用户实例具有相同的 ID:

    1. @Test
    2. public void whenSaveDerivedIdEntity_thenOk() {
    3. User user = new User();
    4. session.save(user);
    5. UserProfile profile = new UserProfile();
    6. profile.setUser(user);
    7. session.save(profile);
    8. assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
    9. }
    Copy

    6. 结论

    在本文中,我们已经了解了在 Hibernate 中定义标识符的多种方法。

    这些示例的完整源代码可以在GitHub 上找到。

  • 相关阅读:
    吴恩达《机器学习》8-5->8-6:特征与直观理解I、样本与值观理解II
    猫头虎分享已解决Bug || SyntaxError: Unexpected token < in JSON at position 0
    3DMAX森林树木植物插件ForestPackLite教程
    宠物店会员管理系统| 宠物店小程序
    JTS:04 读取数据库数据
    BUUCTF·[WUSTCTF2020]大数计算·WP
    静态代码分析是如何工作的
    linux运维笔记:TCP/IP三次握手和四次挥手
    v-html命令渲染的内容,使用scoped属性的情况下,样式不起作用
    Flutter笔记:光影动画按钮、滚动图标卡片组等
  • 原文地址:https://blog.csdn.net/allway2/article/details/127702086