本文介紹了最佳做法是在添加到@ManyToMany的所有者端集合時避免選擇所有行的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!
問題描述
當添加到表示@ManyToMany關聯(lián)的所屬方的集合時,我的JPA實現(xiàn)(Hibernate)將首先選擇關聯(lián)中的所有行,以確定該實體是否已經(jīng)存在于集合中。
我了解這背后的機制,但在處理大型連接表時,這不是很好的性能。當我知道需要插入條目時,避免加載連接表的所有元素的最佳做法是什么?
我將以一個典型的用戶/角色場景為例,為簡潔起見,省略了getters/setters/初始化式:
@Entity
public class User {
@Id
private Long id;
@ManyToMany
private Set<Role> roles;
}
@Entity
public class Role {
@Id
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Set<User> users;
}
我讓User成為擁有方,這樣JPA將跟蹤對User.Roles的更改。
以下代碼導致該問題:
User user = em.find(User.class, 1L);
Role role = em.find(Role.class, 1L);
// This line causes the issue
user.getRoles().add(role);
em.persist(user);
當我添加到用戶角色時,執(zhí)行以下SELECT操作:
select
roles0_.users_id as users_id1_20_0_,
roles0_.roles_id as roles_id2_21_0_,
role1_.id as id1_17_1_,
role1_.name as name2_17_1_
from User_Role roles0_
inner join Role role1_ on roles0_.roles_id=role1_.id
where roles0_.users_id=?
這對于較小的集合來說很好,但對于較大的集合就有問題。
我可以想到以下解決方案,我想知道我應該選擇哪一個,或者是否有更好的方法來做到這一點?
1.執(zhí)行本機查詢:
INSERT INTO User_Role (users_id, roles_id) VALUES (1, 1)
2.為連接表創(chuàng)建實體:
@Entity
IdClass(UserRole.PK)
public class UserRole {
@Id
private User user;
@Id
private Role role;
public static class PK {
private User user;
private Role role;
}
}
然后我可以運行:
User user = em.find(User.class, 1L);
Role role = em.find(Role.class, 1L);
UserRole userRole = new UserRole(user, role);
em.persist(userRole);
我傾向于對INSERT使用原生查詢,但我希望得到一些反饋,了解執(zhí)行此操作的最‘JPA’方式是什么。
推薦答案
《使用Hibernate的Java持久性》一書(第298頁)指出,多對多通常最好使用關聯(lián)類(有點像您在第二個解決方案中已有的UserRole),然后為兩端映射兩個一對多關系–即每個用戶有多個UserRole,每個角色有多個UserRole。這是最”JPA”的做事方式,我想你會得到你想要的表現(xiàn)。
現(xiàn)在細微之處:
-
關聯(lián)類應該有一個基于用戶和角色的ID的組合鍵,而不是它自己的主鍵。本書給出了一個在Association類中創(chuàng)建一個@Embedble靜態(tài)內(nèi)部類的示例,該類包含兩個主要類(在您的例子中是User和Role)的ID。這些ID到關聯(lián)表中的列的映射是在這個內(nèi)部類中完成的。
在您向其傳遞特定角色和用戶的關聯(lián)類的構(gòu)造函數(shù)中,您將填充內(nèi)部類,然后將”this”(即您正在創(chuàng)建的關聯(lián)實例)添加到傳入的用戶和角色的集合中(例如,role.getUserRoles().add(This))。
刪除關聯(lián)時,必須同時從用戶和角色中刪除該關聯(lián)。也就是說,在角色端,您將執(zhí)行以下操作:role.getUserRoles().Remove(UserRole),然后您將在用戶端執(zhí)行相同的操作,然后刪除關聯(lián):ession.ete(UserRole)。
如果您遵循這些步驟,Hibernate將知道正在發(fā)生的一切,并且您的緩存應該是好的。您還可以使用級聯(lián)啟用傳遞性持久性。
編輯:如上所述,這實際上并不會消除試圖避免的查詢。經(jīng)過進一步思考,我沒有一個可以消除查詢的答案,但我可以指出為什么JPA的行為方式如您所見。在原始設置中,每一項都有其他內(nèi)容的集。由于集合是唯一的,并且JPA提供程序遵循集合語義,因此它們必須確保關系是唯一的。因此,如果您添加一個關系,它必須進行查詢以確保該關系不存在。它可以只查詢您試圖添加的關系,也可以查詢整個集合,然后檢查內(nèi)部。如果您要將多個內(nèi)容添加到集合中,則后者是更好的方法,而這正是他們針對此進行優(yōu)化的原因。JPA提供程序永遠不會做的一件事是,如果您試圖添加重復的關系,JPA提供程序永遠不會依賴于底層數(shù)據(jù)庫約束–JPA提供程序更喜歡在Java層處理Java約束
Hibernate還支持袋子選項,該選項允許重復,因此可以避免檢查…但這樣,您的數(shù)據(jù)庫中就會有重復的關系。
這篇關于最佳做法是在添加到@ManyToMany的所有者端集合時避免選擇所有行的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,