Java 永續性/OneToMany
Java 中的 OneToMany 關係是指源物件有一個屬性儲存目標物件的集合,並且如果這些目標物件具有反向關係回到源物件,則它將是 ManyToOne 關係。 Java 和 JPA 中的所有關係都是單向的,也就是說,如果源物件引用目標物件,則不能保證目標物件也與源物件存在關係。 這與關係資料庫不同,在關係資料庫中,關係透過外部索引鍵定義,並進行查詢,以便始終存在反向查詢。
JPA 還定義了 ManyToMany 關係,它類似於 OneToMany 關係,只是反向關係(如果定義)是 ManyToMany 關係。 JPA 中 OneToMany 和 ManyToMany 關係的主要區別在於 ManyToMany 始終使用中間關係聯接表來儲存關係,而 OneToMany 可以使用聯接表,也可以使用目標物件表中的外部索引鍵來引用源物件表的主鍵。 如果 OneToMany 使用目標物件表中的外部索引鍵,JPA 要求關係是雙向的(必須在目標物件中定義反向 ManyToOne 關係),並且源物件必須使用 mappedBy 屬性來定義對映。
在 JPA 中,OneToMany 關係透過 @OneToMany 註解或 <one-to-many> 元素定義。
EMPLOYEE (表)
| EMP_ID | FIRSTNAME | LASTNAME | SALARY | MANAGER_ID |
| 1 | Bob | Way | 50000 | 2 |
| 2 | Sarah | Smith | 75000 | null |
PHONE (表)
| ID | TYPE | AREA_CODE | P_NUMBER | OWNER_ID |
| 1 | home | 613 | 792-0000 | 1 |
| 2 | work | 613 | 896-1234 | 1 |
| 3 | work | 416 | 123-4444 | 2 |
@Entity
public class Employee {
@Id
@Column(name="EMP_ID")
private long id;
...
@OneToMany(mappedBy="owner")
private List<Phone> phones;
...
}
@Entity
public class Phone {
@Id
private long id;
...
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="OWNER_ID")
private Employee owner;
...
}
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id"/>
<one-to-many name="phones" target-entity="org.acme.Phone" mapped-by="owner"/>
</attributes>
</entity>
<entity name="Phone" class="org.acme.Phone" access="FIELD">
<attributes>
<id name="id"/>
<many-to-one name="owner" fetch="LAZY">
<join-column name="OWNER_ID"/>
</many-to-one>
</attributes>
</entity>
請注意,此 @OneToMany 對映需要反向 @ManyToOne 對映才能完成,請參閱 ManyToOne。
關係是雙向的,因此當應用程式更新關係的一端時,另一端也應該更新,並保持同步。 在 JPA 中,與 Java 一樣,維護關係的責任在於應用程式或物件模型。 如果您的應用程式向關係的一端新增內容,那麼它必須向另一端新增內容。
這可以透過物件模型中的 add 或 set 方法解決,這些方法處理關係的兩端,因此應用程式程式碼不需要擔心它。 有兩種方法可以解決這個問題,您可以將關係維護程式碼僅新增到關係的一端,並僅從一端使用 setter(例如,將另一端設為 protected),或者將其新增到兩端,並確保避免無限迴圈。
例如
public class Employee {
private List phones;
...
public void addPhone(Phone phone) {
this.phones.add(phone);
if (phone.getOwner() != this) {
phone.setOwner(this);
}
}
...
}
public class Phone {
private Employee owner;
...
/**
* You have to ensure that the previous owner of this phone is no longer the owner of this
* phone before you attribute it a new owner. Ensure this either by a
* @requires !this.employee.getPhones().contains(this) or by adding to the beginning of
* the method body:
* if(this.employee != null)
* this.employee.removePhone(this);
*/
public void setOwner(Employee employee) {
this.owner = employee;
if (!employee.getPhones().contains(this)) { // warning this may cause performance issues if you have a large data set since this operation is O(n)
employee.getPhones().add(this);
}
}
...
}
有些人希望 JPA 提供商擁有自動維護關係的“魔法”。 這實際上是 EJB CMP 2 規範的一部分。 但是問題是,如果物件被分離或序列化到另一個 VM,或者在被管理之前將新物件關聯起來,或者在 JPA 範圍之外使用物件模型,那麼“魔法”就會消失,應用程式將不得不自己解決問題,因此通常最好將程式碼新增到物件模型中。 但是,一些 JPA 提供商確實支援自動維護關係。
在某些情況下,在新增子物件時例項化大型集合是不可取的。 一個解決方案是不對映雙向關係,而是根據需要查詢它。 此外,一些 JPA 提供商優化了他們的延遲集合物件來處理這種情況,因此您仍然可以向集合新增內容而無需例項化它。
物件和關係表之間的一個常見不匹配是,OneToMany 在 Java 中不需要反向引用,但在資料庫中需要反向引用外部索引鍵。 通常最好在 Java 中定義 ManyToOne 反向引用,如果您不能或不想這樣做,那麼可以使用中間聯接表來儲存關係。 這類似於 ManyToMany 關係,但是如果您向目標外部索引鍵新增唯一約束,則可以強制它是 OneToMany。
JPA 使用 @JoinTable 註解和 <join-table> XML 元素定義聯接表。 JoinTable 可以用於 ManyToMany 或 OneToMany 對映。
另請參閱,單向 OneToMany
EMPLOYEE (表)
| EMP_ID | FIRSTNAME | LASTNAME |
| 1 | Bob | 可以 |
| 2 | Sarah | Smith |
| 3 | Sarah | Smith |
EMP_PHONE (表)
| EMP_ID | PHONE_ID |
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
PHONE (表)
| ID | TYPE | PHONE_ID | P_NUMBER |
| 1 | home | 1 | 792-0000 |
| 2 | work | 1 | 896-1234 |
| 3 | work | 2 | 123-4444 |
@Entity
public class Employee {
@Id
@Column(name="EMP_ID")
private long id;
...
@OneToMany
@JoinTable
(
name="EMP_PHONE",
joinColumns={ @JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID") },
inverseJoinColumns={ @JoinColumn(name="PHONE_ID", referencedColumnName="ID", unique=true) }
)
// While Update this will also insert collection row another insert
private List<Phone> phones;
...
}
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id">
<column name="EMP_ID"/>
</id>
<one-to-many name="phones">
<join-table name="EMP_PHONE">
<join-column name="EMP_ID" referenced-column-name="EMP_ID"/>
<inverse-join-column name="PHONE_ID" referenced-column-name="ID" unique="true" />
</join-table>
</one-to-many>
</attributes>
</entity>
- 參見物件損壞。
JPA 1.0 不支援沒有 JoinTable 的單向 OneToMany 關係。從 JPA 2.0 開始,支援單向 OneToMany。在 JPA 2.x 中,可以在 OneToMany 上使用 @JoinColumn 來定義外部索引鍵,一些 JPA 提供程式可能已經支援這一點。
單向 OneToMany 的主要問題是外部索引鍵由目標物件的表擁有,因此如果目標物件不知道該外部索引鍵,則插入和更新該值很困難。在單向 OneToMany 中,源物件擁有外部索引鍵欄位,並負責更新其值。
單向 OneToMany 中的目標物件是一個獨立的物件,因此它不應該以任何方式依賴於外部索引鍵,即外部索引鍵不能是其主鍵的一部分,也不能通常對其設定非空約束。您可以建模一個物件集合,其中目標沒有對映外部索引鍵,但使用它作為其主鍵,或者使用 Embeddable 集合對映沒有主鍵,請參見嵌入式集合。
如果您的 JPA 提供程式不支援單向 OneToMany 關係,那麼您需要新增一個反向引用 ManyToOne 或 JoinTable。通常,如果您確實想要在資料庫中建模單向 OneToMany,最好使用 JoinTable。
有一些創新的解決方法來定義單向 OneToMany。一種是使用 JoinTable 來對映它,但使目標表成為 JoinTable。這將導致額外的連線,但對於大多數讀取來說可以正常工作,寫入當然不會正常工作,因此這只是一個只讀解決方案,而且是一個很糟糕的解決方案。
EMPLOYEE (表)
| EMP_ID | FIRSTNAME | LASTNAME |
| 1 | Bob | 可以 |
| 2 | Sarah | Smith |
PHONE (表)
| ID | TYPE | AREA_CODE | P_NUMBER | OWNER_ID |
| 1 | home | 613 | 792-0000 | 1 |
| 2 | work | 613 | 896-1234 | 1 |
| 3 | work | 416 | 123-4444 | 2 |
@Entity
public class Employee {
@Id
@Column(name="EMP_ID")
private long id;
...
@OneToMany
@JoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private List<Phone> phones;
...
}
@Entity
public class Phone {
...
@Column(name="OWNER_ID")
private long ownerId;
...
}
如果您的連線列是非空的,則需要指定 @JoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID", nullable = false)
@Entity
public class Employee {
@Id
@Column(name="EMP_ID")
private long id;
...
@OneToMany
@JoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID", nullable = false)
private List<Phone> phones;
...
}
