An Overview of Identifiers in Hibernate/JPA – Hibernate/JPA中的标识符概述

最后修改: 2017年 11月 12日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Identifiers in Hibernate represent the primary key of an entity. This implies the values are unique so that they can identify a specific entity, that they aren’t null and that they won’t be modified.

Hibernate中的标识符代表一个实体的主键。这意味着这些值是唯一的,所以它们可以识别一个特定的实体,它们不是空的,也不会被修改。

Hibernate provides a few different ways to define identifiers. In this article, we’ll review each method of mapping entity ids using the library.

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

2. Simple Identifiers

2.简单标识符

The most straightforward way to define an identifier is by using the @Id annotation.

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

Simple ids are mapped using @Id to a single property of one of these types: Java primitive and primitive wrapper types, String, Date, BigDecimal and BigInteger.

简单的ID使用@Id映射到这些类型中的一个单一属性。Java 原始和原始包装类型、StringDateBigDecimalBigInteger

Let’s see a quick example of defining an entity with a primary key of type long:

让我们看一个快速的例子,定义一个具有long类型主键的实体。

@Entity
public class Student {

    @Id
    private long studentId;
    
    // standard constructor, getters, setters
}

3. Generated Identifiers

3.生成的标识符

If we want to automatically generate the primary key value, we can add the @GeneratedValue annotation.

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

This can use four generation types: AUTO, IDENTITY, SEQUENCE and TABLE.

这可以使用四种生成类型。AUTO、IDENTITY、SEQUENCE和TABLE。

If we don’t explicitly specify a value, the generation type defaults to AUTO.

如果我们没有明确指定一个值,生成类型默认为AUTO。

3.1. AUTO Generation

3.1.AUTO Generation

If we’re using the default generation type, the persistence provider will determine values based on the type of the primary key attribute. This type can be numerical or UUID.

如果我们使用默认的生成类型,持久化提供者将根据主键属性的类型来确定值。这个类型可以是数字或UID

For numeric values, the generation is based on a sequence or table generator, while UUID values will use the UUIDGenerator.

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

Let’s first map an entity primary key using AUTO generation strategy:

让我们首先使用AUTO生成策略映射一个实体的主键。

@Entity
public class Student {

    @Id
    @GeneratedValue
    private long studentId;

    // ...
}

In this case, the primary key values will be unique at the database level.

在这种情况下,主键值在数据库层面将是唯一的。

Now we’ll look at the UUIDGenerator, which was introduced in Hibernate 5.

现在我们来看看UIDGenerator,它是在Hibernate 5.中引入的。

In order to use this feature, we just need to declare an id of type UUID with @GeneratedValue annotation:

为了使用这个功能,我们只需要用@GeneratedValue注解声明一个UUID类型的id。

@Entity
public class Course {

    @Id
    @GeneratedValue
    private UUID courseId;

    // ...
}

Hibernate will generate an id of the form “8dd5f315-9788-4d00-87bb-10eed9eff566”.

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

3.2. IDENTITY Generation

3.2.IDENTITY Generation

This type of generation relies on the IdentityGenerator, which expects values generated by an identity column in the database. This means they are auto-incremented.

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

To use this generation type, we only need to set the strategy parameter:

要使用这种生成类型,我们只需要设置strategy参数。

@Entity
public class Student {

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private long studentId;

    // ...
}

One thing to note is that IDENTITY generation disables batch updates.

有一点需要注意的是,IDENTITY生成禁用了批量更新。

3.3. SEQUENCE Generation

3.3.序列生成

To use a sequence-based id, Hibernate provides the SequenceStyleGenerator class.

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

This generator uses sequences if our database supports them. It switches to table generation if they aren’t supported.

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

In order to customize the sequence name, we can use the @GenericGenerator annotation with SequenceStyleGenerator strategy:

为了定制序列名称,我们可以使用@GenericGenerator注解与SequenceStyleGenerator策略

@Entity
public class User {
    @Id
    @GeneratedValue(generator = "sequence-generator")
    @GenericGenerator(
      name = "sequence-generator",
      strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
      parameters = {
        @Parameter(name = "sequence_name", value = "user_sequence"),
        @Parameter(name = "initial_value", value = "4"),
        @Parameter(name = "increment_size", value = "1")
        }
    )
    private long userId;
    
    // ...
}

In this example, we’ve also set an initial value for the sequence, which means the primary key generation will start at 4.

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

SEQUENCE is the generation type recommended by the Hibernate documentation.

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

The generated values are unique per sequence. If we don’t specify a sequence name, Hibernate will reuse the same hibernate_sequence for different types.

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

3.4. TABLE Generation

3.4.表的生成

The TableGenerator uses an underlying database table that holds segments of identifier generation values.

TableGenerator使用一个底层数据库表,持有标识符生成值的分段。

Let’s customize the table name using the @TableGenerator annotation:

让我们使用@TableGenerator注解来定制表的名称。

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, 
      generator = "table-generator")
    @TableGenerator(name = "table-generator", 
      table = "dep_ids", 
      pkColumnName = "seq_id", 
      valueColumnName = "seq_value")
    private long depId;

    // ...
}

In this example, we can see that we can also customize other attributes such as the pkColumnName and valueColumnName.

在这个例子中,我们可以看到,我们还可以定制其他属性,如pkColumnNamevalueColumnName

However, the disadvantage of this method is that it doesn’t scale well and can negatively affect performance.

然而,这种方法的缺点是,它不能很好地扩展,并会对性能产生负面影响。

To sum up, these four generation types will result in similar values being generated but use different database mechanisms.

总结起来,这四种生成类型将导致生成类似的值,但使用不同的数据库机制。

3.5. Custom Generator

3.5.自定义生成器

Let’s say we don’t want to use any of the out-of-the-box strategies. In order to do that, we can define our custom generator by implementing the IdentifierGenerator interface.

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

We’ll create a generator that builds identifiers containing a String prefix and a number:

我们将创建一个生成器,构建包含String前缀和一个数字的标识符。

public class MyGenerator 
  implements IdentifierGenerator, Configurable {

    private String prefix;

    @Override
    public Serializable generate(
      SharedSessionContractImplementor session, Object obj) 
      throws HibernateException {

        String query = String.format("select %s from %s", 
            session.getEntityPersister(obj.getClass().getName(), obj)
              .getIdentifierPropertyName(),
            obj.getClass().getSimpleName());

        Stream ids = session.createQuery(query).stream();

        Long max = ids.map(o -> o.replace(prefix + "-", ""))
          .mapToLong(Long::parseLong)
          .max()
          .orElse(0L);

        return prefix + "-" + (max + 1);
    }

    @Override
    public void configure(Type type, Properties properties, 
      ServiceRegistry serviceRegistry) throws MappingException {
        prefix = properties.getProperty("prefix");
    }
}

In this example, we override the generate() method from the IdentifierGenerator interface.

在这个例子中,我们从IdentifierGenerator接口重载了generate()方法。

First, we want to find the highest number from the existing primary keys of the form prefix-XX. Then we add 1 to the maximum number found and append the prefix property to get the newly generated id value.

首先,我们要从现有的形式为prefix-XX的主键中找到最高的数字。然后我们在找到的最高数字上加1,并附加prefix属性,得到新生成的id值。

Our class also implements the Configurable interface so that we can set the prefix property value in the configure() method.

我们的类也实现了Configurable接口,因此我们可以在configure()方法中设置prefix属性值。

Next, let’s add this custom generator to an entity.

接下来,让我们把这个自定义生成器添加到一个实体中。

For this, we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:

为此,我们可以使用@GenericGenerator注解,其中的strategy参数包含我们的生成器类的完整类名

@Entity
public class Product {

    @Id
    @GeneratedValue(generator = "prod-generator")
    @GenericGenerator(name = "prod-generator", 
      parameters = @Parameter(name = "prefix", value = "prod"), 
      strategy = "com.baeldung.hibernate.pojo.generator.MyGenerator")
    private String prodId;

    // ...
}

Also, notice we’ve set the prefix parameter to “prod”.

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

Let’s see a quick JUnit test for a clearer understanding of the id values generated:

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

@Test
public void whenSaveCustomGeneratedId_thenOk() {
    Product product = new Product();
    session.save(product);
    Product product2 = new Product();
    session.save(product2);

    assertThat(product2.getProdId()).isEqualTo("prod-2");
}

Here the first value generated using the “prod” prefix was “prod-1”, followed by “prod-2”.

这里使用 “prod “前缀生成的第一个值是 “prod-1″,其次是 “prod-2″。

4. Composite Identifiers

4.复合标识符

Besides the simple identifiers we’ve seen so far, Hibernate also allows us to define composite identifiers.

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

A composite id is represented by a primary key class with one or more persistent attributes.

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

The primary key class must fulfill several conditions:

主键类必须满足几个条件

  • It should be defined using @EmbeddedId or @IdClass annotations.
  • It should be public, serializable and have a public no-arg constructor.
  • Finally, it should implement equals() and hashCode() methods.

The class’s attributes can be basic, composite or ManyToOne, while avoiding collections and OneToOne attributes.

该类的属性可以是基本的、复合的或ManyToOne的,同时避免集合和OneToOne属性。

4.1. @EmbeddedId

4.1.@EmbeddedId

Now let’s look at how to define an id using @EmbeddedId.

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

First, we need a primary key class annotated with @Embeddable:

首先,我们需要一个带有@Embeddable注释的主键类。

@Embeddable
public class OrderEntryPK implements Serializable {

    private long orderId;
    private long productId;

    // standard constructor, getters, setters
    // equals() and hashCode() 
}

Next, we can add an id of type OrderEntryPK to an entity using @EmbeddedId:

接下来,我们可以使用@EmbeddedId向实体添加一个OrderEntryPK类型的id。

@Entity
public class OrderEntry {

    @EmbeddedId
    private OrderEntryPK entryId;

    // ...
}

Let’s see how we can use this type of composite id to set the primary key for an entity:

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

@Test
public void whenSaveCompositeIdEntity_thenOk() {
    OrderEntryPK entryPK = new OrderEntryPK();
    entryPK.setOrderId(1L);
    entryPK.setProductId(30L);
        
    OrderEntry entry = new OrderEntry();
    entry.setEntryId(entryPK);
    session.save(entry);

    assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
}

Here the OrderEntry object has an OrderEntryPK primary id formed of two attributes: orderId and productId.

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

4.2. @IdClass

4.2.@IdClass

The @IdClass annotation is similar to the @EmbeddedId. The difference with @IdClass is that the attributes are defined in the main entity class using @Id for each one. The primary key class will look the same as before.

@IdClass注解与@EmbeddedId类似。与@IdClass不同的是,属性是在主实体类中使用@Id来定义每个属性。主键类看起来和以前一样。

Let’s rewrite the OrderEntry example with an @IdClass:

让我们用@IdClass重写OrderEntry例子。

@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
    @Id
    private long orderId;
    @Id
    private long productId;
    
    // ...
}

Then we can set the id values directly on the OrderEntry object:

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

@Test
public void whenSaveIdClassEntity_thenOk() {        
    OrderEntry entry = new OrderEntry();
    entry.setOrderId(1L);
    entry.setProductId(30L);
    session.save(entry);

    assertThat(entry.getOrderId()).isEqualTo(1L);
}

Note that for both types of composite ids, the primary key class can also contain @ManyToOne attributes.

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

Hibernate also allows defining primary keys made up of @ManyToOne associations combined with @Id annotation. In this case, the entity class should also fulfill the conditions of a primary key class.

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

However, the disadvantage of this method is that there’s no separation between the entity object and the identifier.

然而,这种方法的缺点是,实体对象和标识符之间没有分离。

5. Derived Identifiers

5.衍生的标识符

Derived identifiers are obtained from an entity’s association using the @MapsId annotation.

派生标识符是使用@MapsId注解从一个实体的关联中获得的。

First, let’s create a UserProfile entity that derives its id from a one-to-one association with the User entity:

首先,让我们创建一个UserProfile实体,它的id来自与User实体的一对一关联。

@Entity
public class UserProfile {

    @Id
    private long profileId;
    
    @OneToOne
    @MapsId
    private User user;

    // ...
}

Next, let’s verify that a UserProfile instance has the same id as its associated User instance:

接下来,让我们验证一下UserProfile实例的id是否与其关联的User实例相同。

@Test
public void whenSaveDerivedIdEntity_thenOk() {        
    User user = new User();
    session.save(user);
       
    UserProfile profile = new UserProfile();
    profile.setUser(user);
    session.save(profile);

    assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
}

6. Conclusion

6.结论

In this article, we’ve seen the multiple ways we can define identifiers in Hibernate.

在这篇文章中,我们已经看到了在Hibernate中定义标识符的多种方式。

The full source code of the examples can be found over on GitHub.

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