Mapping a Single Entity to Multiple Tables in JPA – 在JPA中把单个实体映射到多个表上

最后修改: 2019年 10月 27日

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

1. Overview

1.概述

JPA makes dealing with relational database models from our Java applications less painful. Things are simple when we map every table to a single entity class.

JPA使我们在Java应用程序中处理关系型数据库模型时不再那么痛苦了。当我们把每个表都映射到一个实体类时,事情就很简单了。

But we sometimes have reasons to model our entities and tables differently:

但我们有时有理由对我们的实体和表进行不同的建模。

In this short tutorial, we’ll see how to tackle this last scenario.

在这个简短的教程中,我们将看到如何解决这最后一种情况。

2. Data Model

2.数据模型

Let’s say we run a restaurant, and we want to store data about every meal we serve:

比方说,我们经营一家餐馆,我们想存储我们提供的每一餐的数据。

  • Name
  • Description
  • Price
  • What kind of allergens it contains

Since there are many possible allergens, we’re going to group this data set together.

由于有许多可能的过敏原,我们要将这组数据分组。

Furthermore, we’ll also model this using the following table definitions:

此外,我们还将使用以下表格定义进行建模。

meals

Now let’s see how we can map these tables to entities using standard JPA annotations.

现在让我们看看如何使用标准的JPA注解将这些表映射到实体。

3. Creating Multiple Entities

3.创建多个实体

The most obvious solution is to create an entity for both classes.

最明显的解决方案是为这两个类创建一个实体。

Let’s start by defining the Meal entity:

让我们从定义Meal实体开始。

@Entity
@Table(name = "meal")
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @OneToOne(mappedBy = "meal")
    Allergens allergens;

    // standard getters and setters
}

Next, we’ll add the Allergens entity:

接下来,我们将添加Allergens实体。

@Entity
@Table(name = "allergens")
class Allergens {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "meal_id")
    Long mealId;

    @OneToOne
    @PrimaryKeyJoinColumn(name = "meal_id")
    Meal meal;

    @Column(name = "peanuts")
    boolean peanuts;

    @Column(name = "celery")
    boolean celery;

    @Column(name = "sesame_seeds")
    boolean sesameSeeds;

    // standard getters and setters
}

We can see that meal_id is both the primary key and also the foreign key. That means we need to define the one-to-one relationship column using @PrimaryKeyJoinColumn.

我们可以看到, meal_id既是主键,也是外键。这意味着我们需要使用@PrimaryKeyJoinColumn来定义一对一的关系列。

However, this solution has two problems:

然而,这种解决方案有两个问题。

  • We always want to store allergens for a meal, and this solution doesn’t enforce this rule.
  • The meal and allergen data belong together logically. Therefore, we might want to store this information in the same Java class even though we created multiple tables for them.

One possible resolution to the first problem is to add the @NotNull annotation to the allergens field on our Meal entity. JPA won’t let us persist the Meal if we have a null Allergens.

第一个问题的一个可能的解决方案是在我们的Meal实体的allergens字段上添加@NotNull注释。如果我们有一个null Allergens,JPA将不会让我们持久化这个Meal

However, this is not an ideal solution. We want a more restrictive one, where we don’t even have the opportunity to try to persist a Meal without Allergens.

然而,这并不是一个理想的解决方案。我们想要一个更严格的,我们甚至没有机会尝试坚持一个没有过敏原的食物

4. Creating a Single Entity With @SecondaryTable

4.用@SecondaryTable创建单一实体

We can create a single entity specifying that we have columns in different tables using the @SecondaryTable annotation:

我们可以使用@SecondaryTable annotation创建一个单一的实体,指定我们在不同的表中有列。

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

Behind the scenes, JPA joins the primary table with the secondary table and populates the fields. This solution is similar to the @OneToOne relationship, but this way, we can have all of the properties in the same class.

在幕后,JPA将主表与副表连接起来,并填充字段。这种解决方案类似于@OneToOne关系,但这种方式,我们可以将所有的属性放在同一个类中。

It’s important to note that if we have a column that is in a secondary table, we have to specify it with the table argument of the @Column annotation. If a column is in the primary table, we can omit the table argument since JPA looks for columns in the primary table by default.

需要注意的是 如果我们有一个列在二级表中,我们必须用@Column注解的table参数来指定它。如果一个列在主表中,我们可以省略table参数,因为JPA默认是在主表中寻找列。

Also note that we can have multiple secondary tables if we embed them in @SecondaryTables. Alternatively, from Java 8, we can mark the entity with multiple @SecondaryTable annotations since it’s a repeatable annotation.

还要注意的是,如果我们把它们嵌入到@SecondaryTables中,我们可以有多个二级表。另外,从Java 8开始,我们可以用多个@SecondaryTable注释来标记实体,因为它是一个可重复的注释

5. Combining @SecondaryTable With @Embedded

5.将@SecondaryTable@Embedded相结合

As we’ve seen, @SecondaryTable maps multiple tables to the same entity. We also know that @Embedded and @Embeddable do the opposite and map a single table to multiple classes.

正如我们所看到的,@SecondaryTable将多个表映射到同一个实体。我们还知道,@Embedded和@Embeddable的作用正好相反,将一个表映射到多个类

Let’s see what we get when we combine @SecondaryTable with @Embedded and @Embeddable:

让我们看看当我们将@SecondaryTable@Embedded@Embeddable结合起来会得到什么。

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Embedded
    Allergens allergens;

    // standard getters and setters

}

@Embeddable
class Allergens {

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

It’s a similar approach to what we saw using @OneToOne. However, it has a couple of advantages:

这与我们看到的使用@OneToOne的方法类似。然而,它有几个优点。

  • JPA manages the two tables together for us, so we can be sure that there will be a row for each meal in both tables.
  • Also, the code is a bit simpler since we need less configuration.

Nevertheless, this one-to-one-like solution works only when the two tables have matching ids.

尽管如此,这种类似于一对一的解决方案只有在两个表有匹配的id时才会起作用。

It’s worth mentioning that if we want to reuse the Allergens class, it would be better if we defined the columns of the secondary table in the Meal class with @AttributeOverride.

值得一提的是,如果我们想重用Allergens类,如果我们在Meal类中用@AttributeOverride定义二级表的列会更好。

6. Conclusion

6.结语

In this short tutorial, we’ve seen how we can map multiple tables to the same entity using the @SecondaryTable JPA annotation.

在这个简短的教程中,我们已经看到了如何使用@SecondaryTable JPA注解将多个表映射到同一个实体。

We also saw the advantages of combining @SecondaryTable with @Embedded and @Embeddable to get a relationship similar to one-to-one.

我们还看到了将@SecondaryTable@Embedded@Embeddable相结合的优势,以获得类似于一对一的关系。

As usual, the examples are available over on GitHub.

像往常一样,这些例子可以在GitHub上找到over