1. 概述
在本教程中,我们将学习如何使用 JPA 提供的 @Embedded
和 @Embeddable
注解,将一个包含嵌套属性的实体类映射到单一数据库表中。
这种技术非常适合将某些具有业务含义的字段组合成一个类,同时又不希望为它单独建表的场景。
2. 数据模型背景
我们先定义一个名为 company
的数据库表,用于存储公司信息,包括名称、地址、联系电话以及联系人信息。
对应的 Java 类如下:
public class Company {
private Integer id;
private String name;
private String address;
private String phone;
private String contactFirstName;
private String contactLastName;
private String contactPhone;
// standard getters, setters
}
很明显,联系人信息应该抽象成一个独立的类。
但问题在于:我们并不希望为这个联系人信息单独创建一张表。
那么,JPA 提供了什么机制来解决这个问题呢?答案就是 @Embeddable
和 @Embedded
。
3. @Embeddable
@Embeddable
注解用于标记一个类可以被其他实体类嵌入使用。
我们可以将联系人信息封装到一个类中,并使用该注解:
@Embeddable
public class ContactPerson {
private String firstName;
private String lastName;
private String phone;
// standard getters, setters
}
✅ 这个类本身不会被映射成一张表,而是作为其他实体类的一部分存在。
4. @Embedded
@Embedded
注解用于在实体类中嵌入一个 @Embeddable
类型的字段。
接下来我们修改 Company
类,使用 ContactPerson
替代原先的联系人字段:
@Entity
public class Company {
@Id
@GeneratedValue
private Integer id;
private String name;
private String address;
private String phone;
@Embedded
private ContactPerson contactPerson;
// standard getters, setters
}
⚠️ 此时,JPA 会将 ContactPerson
的字段作为 Company
表的列进行映射。
但问题来了:这些字段会被如何命名?默认命名方式是否符合我们的预期?
5. 自定义嵌套字段列名:@EmbeddedColumnNaming
我们可以使用 @EmbeddedColumnNaming
来自定义嵌套字段的列名前缀。
默认情况下,Hibernate 会以嵌套字段名作为前缀。比如 contactPerson.firstName
会被映射为 contactPerson_firstName
。
如果我们想自定义前缀或去掉前缀,可以使用如下方式:
自定义前缀
@Embedded
@EmbeddedColumnNaming("contact_")
private ContactPerson contactPerson;
此时列名会变成 contact_firstName
, contact_lastName
, contact_phone
。
去除前缀
@Embedded
@EmbeddedColumnNaming("")
private ContactPerson contactPerson;
此时列名就是 firstName
, lastName
, phone
。
6. 属性覆盖:@AttributeOverrides
与 @AttributeOverride
在原始的 Company
类中,联系人字段是 contactFirstName
,而在 ContactPerson
类中变成了 firstName
。
JPA 默认会将 firstName
映射为 first_name
,而原来的 phone
字段也会映射为 phone
,这会导致字段冲突。
为了解决这个问题,我们可以使用 @AttributeOverrides
和 @AttributeOverride
来显式指定列名。
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "firstName", column = @Column(name = "contact_first_name")),
@AttributeOverride(name = "lastName", column = @Column(name = "contact_last_name")),
@AttributeOverride(name = "phone", column = @Column(name = "contact_phone"))
})
private ContactPerson contactPerson;
✅ 这样我们就可以精确控制每个字段映射到数据库的列名,避免命名冲突。
⚠️ 注意:这些注解是加在字段上的,因此在不同的实体中可以有不同的映射规则。
7. 总结
本文演示了如何通过 JPA 的 @Embedded
和 @Embeddable
注解,将一个嵌套对象映射到同一个数据库表中。
我们还介绍了以下关键点:
@Embeddable
:定义可被嵌入的类@Embedded
:在实体类中嵌入@Embeddable
类型@EmbeddedColumnNaming
:控制嵌套字段的列名前缀@AttributeOverrides
/@AttributeOverride
:覆盖默认的列名映射,解决字段冲突
通过合理使用这些注解,可以在保持代码结构清晰的同时,避免不必要的数据库表拆分,非常适合用于组合型字段的封装。