1. 概述
在本教程中,我们将了解在 JPA 中创建一对一映射的不同方法。
2. 说明
假设我们正在构建一个用户管理系统,我们的老板要求我们为每个用户存储一个邮寄地址。一个用户将有一个邮寄地址,一个邮寄地址将只有一个与之绑定的用户。
这是用户和 地址实体之间的一对一关系的示例。
让我们在接下来的部分中看看如何实现这一点。
3. 使用外键
3.1. 使用外键建模
让我们看一下下面的 ER 关系图,它表示基于外键的一对一映射:
在此示例中,用户中的address_id列是要寻址的外键。
3.2. 在 JPA 中使用外键实现
首先,让我们创建 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
}
请注意,我们将@OneToOne注释放在相关实体字段“地址”上。
此外,我们需要放置@JoinColumn注释来配置用户表中映射到地址表中主键的列的名称。如果我们不提供名称,Hibernate 将遵循一些规则来选择默认名称。
最后,在下一个实体中请注意,我们不会在那里使用@JoinColumn注释。这是因为我们只需要在外键关系的拥有方。简单地说,无论谁拥有外键列,都会获得@JoinColumn注释。
“地址”实体变得简单一些:
@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
}
我们还需要将@OneToOne注释放在这里。这是因为这是一种双向关系。关系的地址端称为非拥有端。
4. 使用共享主键
4.1. 使用共享主键建模
在此策略中,我们将地址表的主键列 (user_id) 标记为用户表的外键,而不是创建新的列address_id:
我们通过利用这些实体之间具有一对一关系的事实来优化存储空间。
4.2. 在 JPA 中使用共享主键实现
请注意,我们的定义仅略有变化:
@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
}
映射的 By 属性现在已移动到 User 类,因为地址表中现在存在外键。我们还添加了@PrimaryKeyJoinColumn批注,该批注指示 User 实体的主键用作关联地址实体的外键值。
我们仍然必须在 Address 类中定义一个@Id字段。但请注意,这引用了user_id列,并且不再使用@GeneratedValue批注。此外,在引用用户的字段上,我们添加了@MapsId注释,指示将从 User 实体复制主键值。
5. 使用联接表
一对一映射可以有两种类型:可选映射和强制映射。 到目前为止,我们只看到强制性关系。
现在,让我们想象一下,我们的员工与工作站相关联。这是一对一的,但有时员工可能没有工作站,反之亦然。
5.1. 使用联接表建模
到目前为止,我们讨论的策略迫使我们将 null 值放在列中以处理可选关系。
通常,当我们考虑连接表时,我们会想到多对多关系,但是在这种情况下使用连接表可以帮助我们消除这些空值:
现在,每当我们有关系时,我们将在emp_workstation表中创建一个条目,并完全避免空值。
5.2. 在 JPA 中使用联接表实现
我们的第一个例子@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
}
@连接表指示休眠在维护关系时使用连接表策略。
此外,Employee 是此关系的所有者,因为我们选择在其上使用联接表批注。
6. 结论
在本文中,我们学习了在 JPA 和休眠中维护一对一关联的不同方法,以及何时使用每种方法。
本文的源代码可以在 GitHub 上找到。