One-to-One Relationship in JPA – JPA中一对一的关系

最后修改: 2018年 12月 8日

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

1. Overview

1.概述

In this tutorial, we’ll have a look at different ways of creating one-to-one mappings in JPA.

在本教程中,我们将看看在JPA中创建一对一映射的不同方法。

We’ll need a basic understanding of the Hibernate framework, so please check out our guide to Hibernate 5 with Spring for extra background.

我们需要对Hibernate框架有一个基本的了解,所以请查看我们的Hibernate 5 with Spring指南,以了解更多背景。

2. Description

2.描述

Let’s suppose we are building a user management system, and our boss asks us to store a mailing address for each user. A user will have one mailing address, and a mailing address will have only one user tied to it.

假设我们正在建立一个用户管理系统,而我们的老板要求我们为每个用户存储一个邮寄地址。一个用户将有一个邮寄地址,而一个邮寄地址将只有一个用户与之挂钩。

This is an example of a one-to-one relationship, in this case between user and address entities.

这是一个一对一关系的例子,在这种情况下,在用户地址实体之间。

Let’s see how we can implement this in the next sections.

让我们看看在接下来的章节中如何实现这一点。

3. Using a Foreign Key

3.使用外键

3.1. Modeling With a Foreign Key

3.1.用外键建立模型

Let’s have a look at the following ER diagram, which represents a foreign key-based one-to-one mapping:

让我们看一下下面的ER图,它表示一个基于外键的一对一映射。

An ER Diagram mapping Users to Addresses via an address_id foreign key

In this example, the address_id column in users is the foreign key to address.

在这个例子中,users中的address_id列是address外键

3.2. Implementing With a Foreign Key in JPA

3.2.在JPA中用外键实现

First, let’s create the User class and annotate it appropriately:

首先,让我们创建User 类并对其进行适当的注释。

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    //... 

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

    // ... getters and setters
}

Note that we place the @OneToOne annotation on the related entity field, Address.

请注意,我们将@OneToOne注释放在相关实体字段Address上。

Also, we need to place the @JoinColumn annotation to configure the name of the column in the users table that maps to the primary key in the address table. If we don’t provide a name, Hibernate will follow some rules to select a default one.

另外,我们需要放置@JoinColumn annotation来配置users表中与address表中的主键对应的列名。如果我们不提供名称,Hibernate 将遵循一些规则来选择一个默认的名称。

Finally, note in the next entity that we won’t use the @JoinColumn annotation there. This is because we only need it on the owning side of the foreign key relationship. Simply put, whoever owns the foreign key column gets the @JoinColumn annotation.

最后,注意在下一个实体中,我们不会在那里使用@JoinColumn 注解。这是因为我们只在外键关系的拥有方需要它。简单地说,谁拥有外键列,谁就能得到@JoinColumn注解。

The Address entity turns out a bit simpler:

地址实体的结果要简单一些。

@Entity
@Table(name = "address")
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;
    //...

    @OneToOne(mappedBy = "address")
    private User user;

    //... getters and setters
}

We also need to place the @OneToOne annotation here too. That’s because this is a bidirectional relationship. The address side of the relationship is called the non-owning side. 

我们也需要在这里放置@OneToOne注解。这是因为这是一个双向的关系该关系的地址方被称为非所有权方。

4. Using a Shared Primary Key

4.使用共享的主键

4.1. Modeling With a Shared Primary Key

4.1.用共享的主键进行建模

In this strategy, instead of creating a new column address_id, we’ll mark the primary key column (user_id) of the address table as the foreign key to the users table:

在这个策略中,我们没有创建一个新的列address_id,而是将address表中的主键列(user_id)作为users表的外键。

An ER diagram with Users Tied to Addresses where they share the same primary key values

We’ve optimized the storage space by utilizing the fact that these entities have a one-to-one relationship between them.

我们通过利用这些实体之间有一对一的关系来优化存储空间。

4.2. Implementing With a Shared Primary Key in JPA

4.2.在JPA中使用共享主键实现

Notice that our definitions change only slightly:

请注意,我们的定义只是略有变化。

@Entity
@Table(name = "users")
public class User {

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

    //...

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private Address address;

    //... getters and setters
}
@Entity
@Table(name = "address")
public class Address {

    @Id
    @Column(name = "user_id")
    private Long id;

    //...

    @OneToOne
    @MapsId
    @JoinColumn(name = "user_id")
    private User user;
   
    //... getters and setters
}

The mappedBy attribute is now moved to the User class since the foreign key is now present in the address table. We’ve also added the @PrimaryKeyJoinColumn annotation, which indicates that the primary key of the User entity is used as the foreign key value for the associated Address entity.

mappedBy属性现在被移到了User类,因为外键现在存在于address表中。我们还添加了@PrimaryKeyJoinColumn注解,它表明User实体的主键被用作相关Address实体的外键值。

We still have to define an @Id field in the Address class. But note that this references the user_id column, and it no longer uses the @GeneratedValue annotation. Also, on the field that references the User, we’ve added the @MapsId annotation, which indicates that the primary key values will be copied from the User entity.

我们仍然要在Address类中定义一个@Id字段。但是请注意,这引用了user_id列,而且它不再使用@GeneratedValue注解。另外,在引用User的字段上,我们添加了@MapsId注解,这表明主键值将从User实体中复制。

5. Using a Join Table

5.使用连接表

One-to-one mappings can be of two types: optional and mandatory. So far, we’ve seen only mandatory relationships.

一对一映射可以有两种类型:可选的和强制的。到目前为止,我们只看到了强制关系。

Now let’s imagine that our employees get associated with a workstation. It’s one-to-one, but sometimes an employee might not have a workstation and vice versa.

现在让我们想象一下,我们的雇员会与一个工作站相关联。这是一对一的,但有时一个雇员可能没有一个工作站,反之亦然。

5.1. Modeling With a Join Table

5.1.用联接表进行建模

The strategies that we have discussed until now force us to put null values in the column to handle optional relationships.

到目前为止,我们所讨论的策略迫使我们在列中放入空值来处理可选关系。

Typically, we think of many-to-many relationships when we consider a join table, but using a join table in this case can help us to eliminate these null values:

通常,当我们考虑连接表时,我们会想到many-to-many关系但在这种情况下使用连接表可以帮助我们消除这些空值

An ER diagram relating Employees to Workstations via a Join Table

Now, whenever we have a relationship, we’ll make an entry in the emp_workstation table and avoid nulls altogether.

现在,只要我们有一个关系,我们就会在emp_workstation表中建立一个条目,并完全避免空号

5.2. Implementing With a Join Table in JPA

5.2.在JPA中用一个连接表来实现

Our first example used @JoinColumn. This time, we’ll use @JoinTable:

我们的第一个例子使用了@JoinColumn。这一次,我们将使用@JoinTable

@Entity
@Table(name = "employee")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //...

    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "emp_workstation", 
      joinColumns = 
        { @JoinColumn(name = "employee_id", referencedColumnName = "id") },
      inverseJoinColumns = 
        { @JoinColumn(name = "workstation_id", referencedColumnName = "id") })
    private WorkStation workStation;

    //... getters and setters
}
@Entity
@Table(name = "workstation")
public class WorkStation {

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

    //...

    @OneToOne(mappedBy = "workStation")
    private Employee employee;

    //... getters and setters
}

@JoinTable instructs Hibernate to employ the join table strategy while maintaining the relationship.

@JoinTable指示Hibernate采用连接表策略,同时保持关系。

Also, Employee is the owner of this relationship, as we chose to use the join table annotation on it.

另外,Employee是这个关系的所有者,因为我们选择了对它使用连接表注释。

6. Conclusion

6.结语

In this article, we learned different ways of maintaining a one-to-one association in JPA and Hibernate, and when to use each.

在这篇文章中,我们学习了在JPA和Hibernate中维护一对一关联的不同方法,以及何时使用每种方法。

The source code for this article can be found over on GitHub.

这篇文章的源代码可以在GitHub上找到over