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的setIsNullAllowedAPI 來設定。
- 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