Java 持久化/嵌入式对象
在应用程序对象模型中,一些对象被认为是独立的,而另一些则被认为是其他对象的依赖部分。在 UML 中,对依赖对象的关联被认为是聚合或组合关联。在关系数据库中,这种关联关系可以通过两种方式建模:依赖对象可以拥有自己的表,或者其数据可以嵌入到独立对象的表中。这也就是依赖数据被包含在独立对象的表中的另一种说法。
在 JPA 中,目标对象的数据嵌入到源对象表中的关联关系被称为嵌入式关系,而目标对象被称为嵌入式对象。嵌入式对象与实体对象具有不同的要求和限制,它们由@Embeddable
注解或<embeddable>
元素定义。
嵌入式对象不能直接持久化或查询,它只能在嵌入它的源对象的上下文中进行持久化或查询。嵌入式对象没有 ID 或表。JPA 规范不支持嵌入式对象具有继承,尽管一些 JPA 提供商可能允许这样做。JPA 2.0 支持来自嵌入式对象的关系关系。
对嵌入式对象的关联关系通过@Embedded
注解或<embedded>
元素定义。JPA 2.0 规范还支持对嵌入式对象的集合 关系。
@Embeddable
public class EmploymentPeriod {
@Column(name="START_DATE")
private java.sql.Date startDate;
@Column(name="END_DATE")
private java.sql.Date endDate;
....
}
<embeddable class="org.acme.EmploymentPeriod" access="FIELD">
<attributes>
<basic name="startDate">
<column name="START_DATE"/>
</basic>
<basic name="endDate">
<column name="END_DATE"/>
</basic>
</attributes>
</embeddable>
@Entity
public class Employee {
@Id
private long id;
...
@Embedded
private EmploymentPeriod period;
...
}
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id"/>
<embedded name="period"/>
</attributes>
</entity>
嵌入式对象实例可以被嵌入到同一个父实体中多次。但是,如果没有额外的措施,这会产生命名冲突,因为这将需要在表中拥有两个或多个同名列,这是不可能的。所有(除一个外)相同类型的嵌入式实例的列需要重新命名以确保唯一的列名。这种重命名是通过@AttributeOverride 注解、<attribute-override>
元素、@AssociationOverride 注解或<association-override>
元素完成的。
- @AttributeOverride 用于重命名基本映射。
- @AssociationOverride 用于重命名实体关系的映射。
- 例如,@AssociationOverride 适用于嵌入式类中一个属性,该属性是 @ManyToOne 关系或 @OneToOne 关系。即当嵌入式类持有外键时。
@AttributeOverride 和 @AssociationOverride 注解分别与@AttributeOverrides 和@AssociationOverrides 注解分组在一起(注意复数形式)。
/** An Entity to which the Embeddable has a ManyToOne relation */
@Entity
public class Location {
@Id
private java.lang.Integer id;
...
}
/** Embeddable that will be embed twice in the same table */
@Embeddable
public class User {
// Basic mapping
@Column(name="USER_NAME")
private java.lang.String userName;
// Basic mapping
@Column(name="USER_ACCOUNT")
private java.lang.Integer userAccount;
// Relationship
@ManyToOne()
@JoinColumn(name = "LOCATION_ID", referencedColumnName = "id")
public Location location;
...
}
/** Entity holding two instances of the same Embeddable type */
@Entity
public class Employee {
@Id
private long id;
...
// Embed an instance of User
@Embedded
// rename the basic mappings
@AttributeOverrides({
@AttributeOverride(name="userName", column=@Column(name="EMPLOYEE_NAME")),
@AttributeOverride(name="userAccount", column=@Column(name="EMPLOYEE_ACCOUNT"))
})
// rename the relationship
@AssociationOverrides({
@AssociationOverride(name = "location",
joinColumns = @JoinColumn(name = "EMPLOYEE_LOCATION_ID"))
})
private User employee;
// Embed a second instance of User
@Embedded
// rename the basic mappings
@AttributeOverrides({
@AttributeOverride(name="userName", column=@Column(name="SUPERVISOR_NAME")),
@AttributeOverride(name="userAccount", column=@Column(name="SUPERVISOR_ACCOUNT"))
})
// rename the relationship
@AssociationOverrides({
@AssociationOverride(name = "location",
joinColumns = @JoinColumn(name = "SUPERVISOR_LOCATION_ID"))
})
private User supervisor;
...
}
嵌入式对象可以在多个类之间共享。考虑一个Name
对象,它同时被Employee
和User
包含。Employee
和User
都拥有自己的表,它们希望以不同的列名存储姓名。嵌入式对象通过允许每个嵌入式映射覆盖嵌入式对象中使用的列来支持这一点。这通过@AttributeOverride 注解或<attribute-override>
元素完成。
请注意,嵌入式对象不能在多个实例之间共享。如果你希望共享嵌入式对象实例,那么你必须将其变成一个拥有自己表的独立对象。
@Entity
public class Employee {
@Id
private long id;
...
@Embedded
@AttributeOverrides({
@AttributeOverride(name="startDate", column=@Column(name="START_DATE")),
@AttributeOverride(name="endDate", column=@Column(name="END_DATE"))
})
private Period employmentPeriod;
...
}
@Entity
public class User {
@Id
private long id;
...
@Embedded
@AttributeOverrides({
@AttributeOverride(name="startDate", column=@Column(name="SDATE")),
@AttributeOverride(name="endDate", column=@Column(name="EDATE"))
})
private Period period;
...
}
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id"/>
<embedded name="employmentPeriod">
<attribute-override name="startDate">
<column name="START_DATE"/>
</attribute-override>
<attribute-override name="endDate">
<column name="END_DATE"/>
</attribute-override>
</embedded>
</attributes>
</entity>
<entity name="User" class="org.acme.User" access="FIELD">
<attributes>
<id name="id"/>
<embedded name="period">
<attribute-override name="startDate">
<column name="SDATE"/>
</attribute-override>
<attribute-override name="endDate">
<column name="EDATE"/>
</attribute-override>
</embedded>
</attributes>
</entity>
EmbeddedId
是一个包含实体Id
的嵌入式对象。
参见:嵌入式 ID
可嵌入对象的数据包含在其父表中的多个列中。由于没有单个字段值,因此无法知道父对象对可嵌入对象的引用是否为null
。可以假设,如果可嵌入对象的每个字段值都为null
,那么引用应该为null
,但随后就没有办法表示所有值为null
的可嵌入对象。JPA不允许可嵌入对象为null
,但一些JPA提供程序可能支持这一点。
- TopLink / EclipseLink : 支持嵌入引用为
null
。这可以通过使用DescriptorCustomizer
和AggregateObjectMapping
的setIsNullAllowed
API 来设置。
- Hibernate : 在加载实体时,如果可嵌入对象中的所有列都为null,则嵌入引用将设置为
null
。
嵌套
[edit | edit source]嵌套的可嵌入对象是指从另一个可嵌入对象到可嵌入对象的关系。JPA 1.0规范只允许可嵌入对象中存在Basic
关系,因此不支持嵌套的可嵌入对象,但是一些JPA产品可能支持它们。从技术上讲,没有什么可以阻止在可嵌入对象中使用@Embedded
注释,因此这可能根据您的JPA提供程序(祈祷吧)而生效。
JPA 2.0 支持嵌套的可嵌入对象。
- TopLink / EclipseLink : 支持来自可嵌入对象的嵌入映射。可以使用现有的
@Embedded
注释或<embedded>
元素。
对于嵌套的可嵌入对象以及通常的可嵌入对象,可以使用属性访问,并为嵌套的可嵌入对象的每个属性添加get/set方法,作为一种变通方案。
使用属性定义嵌套的可嵌入对象的示例
[edit | edit source]@Embeddable
public class EmploymentDetails {
private EmploymentPeriod period;
private int yearsOfService;
private boolean fullTime;
....
public EmploymentDetails() {
this.period = new EmploymentPeriod();
}
@Transient
public EmploymentPeriod getEmploymentPeriod() {
return period;
}
@Basic
public Date getStartDate() {
return getEmploymentPeriod().getStartDate();
}
public void setStartDate(Date startDate) {
getEmploymentPeriod().setStartDate(startDate);
}
@Basic
public Date getEndDate() {
return getEmploymentPeriod().getEndDate();
}
public void setEndDate(Date endDate) {
getEmploymentPeriod().setEndDate(endDate);
}
....
}
继承
[edit | edit source]可嵌入继承是指一个可嵌入类子类化另一个可嵌入类。JPA规范不允许可嵌入对象中的继承,但是一些JPA产品可能支持这一点。从技术上讲,没有什么可以阻止在可嵌入对象中使用@DiscriminatorColumn
注释,因此这可能根据您的JPA提供程序(祈祷吧)而生效。可嵌入对象中的继承始终是单表,因为可嵌入对象必须位于其父表的内部。通常,尝试在可嵌入对象和实体之间混合继承不是一个好主意,但在某些情况下可能有效。
- TopLink / EclipseLink : 支持可嵌入对象的继承。这可以通过使用
DescriptorCustomizer
和InheritancePolicy
来设置。
关系
[edit | edit source]关系是指可嵌入对象具有对实体的OneToOne
或其他此类映射。JPA 1.0规范只允许可嵌入对象中存在Basic
映射,因此不支持来自可嵌入对象的关系,但是一些JPA产品可能支持它们。从技术上讲,没有什么可以阻止在可嵌入对象中使用@OneToOne
注释或其他关系,因此这可能根据您的JPA提供程序(祈祷吧)而生效。
JPA 2.0 支持来自可嵌入对象的任何类型的关系。
- TopLink / EclipseLink : 支持来自可嵌入对象的关系映射。可以使用现有的关系注释或XML元素。
来自除可嵌入对象的父对象之外的实体到可嵌入对象的关系通常不是一个好主意,因为可嵌入对象是其父对象的私有依赖部分。通常,关系应该指向可嵌入对象的父对象,而不是可嵌入对象本身。否则,通常最好将可嵌入对象变成具有自己表的独立实体。如果可嵌入对象具有双向关系,例如需要反向ManyToOne
的OneToMany
,则反向关系应该指向可嵌入对象的父对象。
对于从可嵌入对象建立关系,一种变通方案是在可嵌入对象的父对象中定义关系,并为该关系定义属性get/set方法,这些方法将关系设置到可嵌入对象中。
从其父对象在可嵌入对象中设置关系的示例
[edit | edit source]@Entity
public class Employee {
....
private EmploymentDetails details;
....
@Embedded
public EmploymentDetails getEmploymentDetails() {
return details;
}
@OneToOne
public Address getEmploymentAddress() {
return getEmploymentDetails().getAddress();
}
public void setEmploymentAddress(Address address) {
getEmploymentDetails().setAddress(address);
}
}
可嵌入对象中有时需要的一种特殊关系是对其父对象的关系。JPA不支持此关系,但一些JPA提供程序可能支持。
- Hibernate : 支持可嵌入对象中的
@Parent
注释以定义与其父对象的关系。
对于从可嵌入对象建立父对象关系,一种变通方案是在属性set方法中设置父对象。
在可嵌入对象中将其关系设置到其父对象的示例
[edit | edit source]@Entity
public class Employee {
....
private EmploymentDetails details;
....
@Embedded
public EmploymentDetails getEmploymentDetails() {
return details;
}
public void setEmploymentDetails(EmploymentDetails details) {
this.details = details;
details.setParent(this);
}
}
集合
[edit | edit source]可嵌入对象的集合类似于OneToMany
,只是目标对象是可嵌入的并且没有Id
。这允许定义OneToMany
而没有反向ManyToOne
,因为父对象负责在目标对象的表中存储外键。JPA 1.0不支持可嵌入对象的集合,但一些JPA提供程序支持这一点。
JPA 2.0 通过ElementCollection
映射支持可嵌入对象的集合。请参阅,ElementCollection.
- EclipseLink (截至1.2) : 支持JPA 2.0
ElementCollection
映射。
- TopLink / EclipseLink : 支持可嵌入对象的集合。这可以通过使用
DescriptorCustomizer
和AggregateCollectionMapping
来设置。
- Hibernate : 通过
@CollectionOfElements
注释和JPA 2.0ElementCollection
映射支持可嵌入对象的集合。
- DataNucleus : 支持JPA 2.0
ElementCollection
映射。
通常,目标表的主键将由父对象的主键和可嵌入对象中的一些唯一字段组成。可嵌入对象在其父对象的集合中应该具有唯一字段,但对于整个类而言并不需要唯一。它仍然可以具有唯一id并仍然使用排序,或者如果它没有唯一字段,则其id可以由其所有字段组成。可嵌入集合对象将不同于典型可嵌入对象,因为它不会存储在父对象的表中,而是存储在它自己的表中。可嵌入对象是严格私有的对象,删除父对象将导致删除可嵌入对象,从可嵌入集合中删除应该导致删除可嵌入对象。可嵌入对象无法直接查询,并且不是独立对象,因为它们没有Id
。
查询
[edit | edit source]无法直接查询可嵌入对象,但可以在其父对象的上下文中查询它们。通常,最好选择父对象,然后从父对象访问可嵌入对象。这将确保可嵌入对象在持久化上下文中注册。如果在查询中选择可嵌入对象,则结果对象将分离,并且不会跟踪更改。
查询可嵌入对象的示例
[edit | edit source]SELECT employee.period from Employee employee where employee.period.endDate = :param