Hibernate Inheritance Mapping – Hibernate 继承映射

最后修改: 2017年 11月 26日

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

1. Overview

1.概述

Relational databases don’t have a straightforward way to map class hierarchies onto database tables.

关系型数据库没有一种直接的方法来将类的层次结构映射到数据库表中。

To address this, the JPA specification provides several strategies:

为了解决这个问题,JPA规范提供了几种策略。

  • MappedSuperclass – the parent classes, can’t be entities
  • Single Table – The entities from different classes with a common ancestor are placed in a single table.
  • Joined Table – Each class has its table, and querying a subclass entity requires joining the tables.
  • Table per Class – All the properties of a class are in its table, so no join is required.

Each strategy results in a different database structure.

每种策略都会产生不同的数据库结构。

Entity inheritance means that we can use polymorphic queries for retrieving all the subclass entities when querying for a superclass.

实体继承意味着我们在查询超类时可以使用多态查询来检索所有子类实体。

Since Hibernate is a JPA implementation, it contains all of the above as well as a few Hibernate-specific features related to inheritance.

由于Hibernate是一个JPA的实现,它包含了以上所有的内容,以及一些与继承有关的Hibernate特有的功能。

In the next sections, we’ll go over available strategies in more detail.

在接下来的章节中,我们将更详细地介绍可用的策略。

2. MappedSuperclass

2.MappedSuperclass

Using the MappedSuperclass strategy, inheritance is only evident in the class but not the entity model.

使用MappedSuperclass策略,继承只在类中明显,而在实体模型中不明显。

Let’s start by creating a Person class that will represent a parent class:

让我们先创建一个Person类,它将代表一个父类。

@MappedSuperclass
public class Person {

    @Id
    private long personId;
    private String name;

    // constructor, getters, setters
}

Notice that this class no longer has an @Entity annotation, as it won’t be persisted in the database by itself.

注意,这个类不再有@Entity注解,因为它本身不会被持久化在数据库中。

Next, let’s add an Employee subclass:

接下来,让我们添加一个Employee子类。

@Entity
public class MyEmployee extends Person {
    private String company;
    // constructor, getters, setters 
}

In the database, this will correspond to one MyEmployee table with three columns for the declared and inherited fields of the subclass.

在数据库中,这将对应于一个MyEmployee表,其中有三列是子类声明和继承的字段。

If we’re using this strategy, ancestors cannot contain associations with other entities.

如果我们使用这种策略,祖先不能包含与其他实体的关联。

3. Single Table

3.单人桌

The Single Table strategy creates one table for each class hierarchy. JPA also chooses this strategy by default if we don’t specify one explicitly.

单表策略为每个类的层次结构创建一个表。如果我们没有明确指定,JPA也默认选择这个策略。

We can define the strategy we want to use by adding the @Inheritance annotation to the superclass:

我们可以通过向超类添加@Inheritance注解来定义我们要使用的策略。

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class MyProduct {
    @Id
    private long productId;
    private String name;

    // constructor, getters, setters
}

The identifier of the entities is also defined in the superclass.

实体的标识符也被定义在超类中。

Then we can add the subclass entities:

然后我们可以添加子类实体。

@Entity
public class Book extends MyProduct {
    private String author;
}
@Entity
public class Pen extends MyProduct {
    private String color;
}

3.1. Discriminator Values

3.1.鉴别器数值

Since the records for all entities will be in the same table, Hibernate needs a way to differentiate between them.

由于所有实体的记录都将在同一个表中,Hibernate需要一种方法来区分它们。

By default, this is done through a discriminator column called DTYPE that has the name of the entity as a value.

默认情况下,这是通过一个名为DTYPE的判别列来完成的,该列将实体的名称作为一个值。

To customize the discriminator column, we can use the @DiscriminatorColumn annotation:

为了定制判别器列,我们可以使用@DiscriminatorColumn注释。

@Entity(name="products")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="product_type", 
  discriminatorType = DiscriminatorType.INTEGER)
public class MyProduct {
    // ...
}

Here we’ve chosen to differentiate MyProduct subclass entities by an integer column called product_type.

在这里,我们选择通过一个名为product_typeinteger列来区分MyProduct子类实体。

Next, we need to tell Hibernate what value each subclass record will have for the product_type column:

接下来,我们需要告诉Hibernate每个子类记录的product_type列将有什么值。

@Entity
@DiscriminatorValue("1")
public class Book extends MyProduct {
    // ...
}
@Entity
@DiscriminatorValue("2")
public class Pen extends MyProduct {
    // ...
}

Hibernate adds two other predefined values that the annotation can take — null and not null:

Hibernate增加了另外两个预定义的值,注释可以采取–nullnot null

  • @DiscriminatorValue(“null”) means that any row without a discriminator value will be mapped to the entity class with this annotation; this can be applied to the root class of the hierarchy.
  • @DiscriminatorValue(“not null”) – Any row with a discriminator value not matching any of the ones associated with entity definitions will be mapped to the class with this annotation.

Instead of a column, we can also use the Hibernate-specific @DiscriminatorFormula annotation to determine the differentiating values:

我们也可以使用Hibernate特有的@DiscriminatorFormula注解来确定区分值,而不是一列。

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when author is not null then 1 else 2 end")
public class MyProduct { ... }

This strategy has the advantage of polymorphic query performance since only one table needs to be accessed when querying parent entities.

这种策略具有多态查询性能的优势,因为在查询父实体时只需要访问一个表。

On the other hand, this also means that we can no longer use NOT NULL constraints on subclass entity properties.

另一方面,这也意味着我们不能再对子类实体属性使用NOT NULL约束。

4. Joined Table

4.加入表格

Using this strategy, each class in the hierarchy is mapped to its table. The only column that repeatedly appears in all the tables is the identifier, which will be used for joining them when needed.

使用这种策略,层次结构中的每个类都被映射到它的表中。唯一在所有表中重复出现的列是标识符,它将在需要时用于连接它们。

Let’s create a superclass that uses this strategy:

让我们创建一个使用这个策略的超类。

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {
    @Id
    private long animalId;
    private String species;

    // constructor, getters, setters 
}

Then we can simply define a subclass:

那么我们就可以简单地定义一个子类。

@Entity
public class Pet extends Animal {
    private String name;

    // constructor, getters, setters
}

Both tables will have an animalId identifier column.

两个表都将有一个animalId标识符列。

The primary key of the Pet entity also has a foreign key constraint to the primary key of its parent entity.

Pet实体的主键对其父实体的主键也有一个外键约束。

To customize this column, we can add the @PrimaryKeyJoinColumn annotation:

为了定制这个列,我们可以添加@PrimaryKeyJoinColumn注释。

@Entity
@PrimaryKeyJoinColumn(name = "petId")
public class Pet extends Animal {
    // ...
}

The disadvantage of this inheritance mapping method is that retrieving entities requires joins between tables, which can result in lower performance for large numbers of records.

这种继承映射方法的缺点是,检索实体需要在表之间进行连接,这可能导致大量记录的性能降低。

The number of joins is higher when querying the parent class because it will join with every single related child — so performance is more likely to be affected the higher up the hierarchy we want to retrieve records.

当查询父类时,连接的数量较多,因为它将与每一个相关的子类连接–因此,我们想要检索的记录的层次越高,性能就越可能受到影响。

5. Table per Class

5.每班表

The Table per Class strategy maps each entity to its table, which contains all the properties of the entity, including the ones inherited.

每类表策略将每个实体映射到它的表,其中包含实体的所有属性,包括继承的属性。

The resulting schema is similar to the one using @MappedSuperclass. But Table per Class will indeed define entities for parent classes, allowing associations and polymorphic queries as a result.

产生的模式与使用@MappedSuperclass的模式类似。但是每类表确实将为父类定义实体,因此允许关联和多态查询。

To use this strategy, we only need to add the @Inheritance annotation to the base class:

要使用这个策略,我们只需要在基类中添加@Inheritance注解。

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
    @Id
    private long vehicleId;

    private String manufacturer;

    // standard constructor, getters, setters
}

Then we can create the subclasses in the standard way.

然后我们可以用标准的方式创建子类。

This is not that different from merely mapping each entity without inheritance. The distinction is clear when querying the base class, which will return all the subclass records as well by using a UNION statement in the background.

这与仅仅映射每个实体而不继承并没有什么不同。在查询基类时,区别很明显,通过在后台使用UNION语句,也会返回所有的子类记录。

The use of UNION can also lead to inferior performance when choosing this strategy. Another issue is that we can no longer use identity key generation.

选择这种策略时,使用UNION也会导致性能下降。另一个问题是,我们不能再使用身份密钥生成。

6. Polymorphic Queries

6.多态查询

As mentioned, querying a base class will retrieve all the subclass entities as well.

如前所述,查询一个基类也会检索到所有的子类实体。

Let’s see this behavior in action with a JUnit test:

让我们通过一个JUnit测试来看看这种行为的作用。

@Test
public void givenSubclasses_whenQuerySuperclass_thenOk() {
    Book book = new Book(1, "1984", "George Orwell");
    session.save(book);
    Pen pen = new Pen(2, "my pen", "blue");
    session.save(pen);

    assertThat(session.createQuery("from MyProduct")
      .getResultList()).hasSize(2);
}

In this example, we’ve created two Book and Pen objects and then queried their superclass MyProduct to verify that we’ll retrieve two objects.

在这个例子中,我们创建了两个BookPen对象,然后查询了它们的超类MyProduct以验证我们将检索到两个对象。

Hibernate can also query interfaces or base classes that are not entities but are extended or implemented by entity classes.

Hibernate还可以查询那些不是实体但被实体类扩展或实现的接口或基类。

Let’s see a JUnit test using our @MappedSuperclass example:

让我们看看使用我们的@MappedSuperclass例子的JUnit测试。

@Test
public void givenSubclasses_whenQueryMappedSuperclass_thenOk() {
    MyEmployee emp = new MyEmployee(1, "john", "baeldung");
    session.save(emp);

    assertThat(session.createQuery(
      "from com.baeldung.hibernate.pojo.inheritance.Person")
      .getResultList())
      .hasSize(1);
}

Note that this also works for any superclass or interface, whether it’s a @MappedSuperclass or not. The difference from a usual HQL query is that we have to use the fully qualified name since they are not Hibernate-managed entities.

注意,这也适用于任何超类或接口,无论它是否是@MappedSuperclass。与通常的HQL查询不同的是,我们必须使用完全合格的名称,因为它们不是Hibernate管理的实体。

If we don’t want a subclass to be returned by this type of query, we only need to add the Hibernate @Polymorphism annotation to its definition, with type EXPLICIT:

如果我们不希望子类被这种类型的查询所返回,我们只需要在其定义中添加Hibernate @Polymorphism 注解,类型为EXPLICIT

@Entity
@Polymorphism(type = PolymorphismType.EXPLICIT)
public class Bag implements Item { ...}

In this case, when querying for Items, the Bag records won’t be returned.

在这种情况下,当查询Items时,Bag记录将不会被返回。

7. Conclusion

7.结论

In this article, we’ve shown the different strategies for mapping inheritance in Hibernate.

在这篇文章中,我们已经展示了Hibernate中映射继承的不同策略。

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

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