盡管如此, Burt Beckwith在演講中指出的有關映射集合的性能問題通常適用于每個啟用Hibernate的應用程序。 這就是為什么在觀看他的演示文稿后,我才意識到他的提議正是我自己一直在做的事情,并指示我的同事在使用Hibernate中的 映射集合進行開發時應該做。
以下是使用Hibernate 映射集合時要考慮的5件事:
讓我們考慮以下經典的“圖書館–訪問”示例:
以下Library類具有Visit實例的集合:
package eg;
import java.util.Set;public class Library {private long id;private Set visits;public long getId() { return id; }private void setId(long id) { this.id=id; }private Set getVisits() { return visits; }private void setVisits(Set visits) { this.visits=visits; }........
}
以下是Visit類:
package eg;
import java.util.Set;public class Visit {private long id;private String personName;public long getId() { return id; }private void setId(long id) { this.id=id; }private String getPersonName() { return personName; }private void setPersonName(String personName) { this.personName=personName; }........
}
假設一個庫有多個唯一的訪問,并且每個訪問都與一個不同的庫相關聯,則可以使用單向 的一對多關聯,如下所示:
<hibernate-mapping><class name="Library"><id name="id"><generator class="sequence"/></id><set name="visits"><key column="library_id" not-null="true"/><one-to-many class="Visit"/></set></class><class name="Visit"><id name="id"><generator class="sequence"/></id><property name="personName"/></class></hibernate-mapping>
我還將提供上述模式的表定義示例:
create table library (id bigint not null primary key )
create table visit(id bigint not nullprimary key,personName varchar(255),library_id bigint not null)
alter table visit add constraint visitfk0 (library_id) references library
那么這張照片怎么了?
當您嘗試添加到映射的集合時,可能會出現性能瓶頸。 如您所見,集合被實現為Set 。 集合保證其所包含元素之間的唯一性。 那么, Hibernate如何知道一個新項目是唯一的以便將其添加到Set中呢? 好吧,不要驚訝; 添加到Set中需要從數據庫加載所有可用項。 Hibernate將每一個都與新的進行比較,以確保唯一性。 此外,以上是我們無法繞過的標準行為,即使我們由于業務規則而知道新項目是唯一的!
在映射集合中使用List實現也無法解決向其中添加項目時的性能瓶頸問題。 盡管列表不保證唯一性,但它們可以保證項目的順序。 因此,為了在映射的List中保持正確的項目順序,即使我們要添加到列表的末尾, Hibernate也必須提取整個集合。
我認為,添加一個新的訪問 圖書館是很長的路要走,您不同意嗎?
此外,上面的示例在開發中非常有效,我們只有很少的訪問。 在每個庫可能有數百萬訪問量的生產環境中,請想象一下當您嘗試再添加一個時會降低性能!
為了克服上述性能問題,我們可以將集合映射為Bag ,這只是一個常規集合,沒有順序或唯一性保證,但是在這樣做之前,請考慮下面的最后一點。
當您從集合中刪除對象或向集合中添加對象時,集合所有者的版本號會增加。 因此,當同時進行訪問創建時,在Library對象上存在人為的樂觀鎖定異常的高風險。 我們將樂觀的鎖定異常描述為“人為的”,因為它們發生在集合所有者對象( Library )上,當我們從Visits集合中添加/刪除項目時,我們不認為自己正在編輯(但實際上是!)。
我要指出的是,相同的規則適用于多對多關聯類型。
那么解決方案是什么?
解決方案很簡單,從所有者( Library )對象中刪除映射的集合 ,然后“手動”執行Visit項目的插入和刪除。 提議的解決方案通過以下方式影響使用:
- 要將訪問添加到庫中,我們必須創建一個新的“ 訪問”項,將其與“ 庫”項相關聯,并將其顯式保存在數據庫中。
- 要從圖書館中刪除訪問 ,我們必須搜索“訪問”表,找到我們需要的確切記錄并將其刪除。
- 使用建議的解決方案,不支持級聯。 要刪除資料庫,您需要先刪除(取消關聯)其所有訪問記錄。
為了保持環境整潔有序,您可以通過實現一個助手方法將“訪問”偽集合恢復到Library對象,該方法將查詢數據庫并返回與特定Library關聯的所有Visit對象。 此外,您可以在Visit項目中實現幾個幫助程序方法,這些方法將執行實際的訪問記錄插入和刪除操作。
下面,我們提供Library類, Visit類和Hibernate映射的更新版本,以便符合我們提出的解決方案:
首先更新的庫類:
package eg;
import java.util.Set;public class Library {private long id;public long getId() { return id; }private void setId(long id) { this.id=id; }public Set getVisits() { // TODO : return select * from visit where visit.library_id=this.id}........
}
如您所見,我們刪除了映射的集合,并引入了方法“ getVisits() ”,該方法應用于返回特定Library實例的所有Visit項目(TODO注釋為偽代碼)。
以下是更新的Visit類:
package eg;
import java.util.Set;public class Visit {private long id;private String personName;private long library_id;public long getId() { return id; }private void setId(long id) { this.id=id; }private String getPersonName() { return personName; }private void setPersonName(String personName) { this.personName=personName; }private long getLibrary_id() { return library_id; }private void setLibrary_id(long library_id) { this. library_id =library_id; }........
}
如您所見,我們已經在Visit對象中添加了“ library_id ”字段,以便能夠將其與Library項目相關聯。
最后是更新的Hibernate映射:
<hibernate-mapping><class name="Library"><id name="id"><generator class="sequence"/></id></class><class name="Visit"><id name="id"><generator class="sequence"/></id><property name="personName"/><property name="library_id"/></class></hibernate-mapping>
因此,永遠不要在Hibernate中使用映射的集合嗎?
好吧,說實話,不。您需要檢查每個案例,以便決定要做什么。 如果收集的數量較小,則標準方法很好-在多對多關聯方案的情況下,雙方都是如此。 此外,集合將包含代理,因此在初始化之前,它們將小于實際實例。
編碼愉快! 別忘了分享!
賈斯汀
聚苯乙烯
在TheServerSide上對這篇文章進行了相當長的辯論之后,一個或我們的讀者Eb Bras提供了一個有用的Hibernate“技巧和竅門”列表,讓他看看該說些什么:
這是我長期記錄的一些Hibernate提示和技巧:
反=“真”
在一對多的父子關聯(與另一個實體或用作一個實體的值類型)中盡可能多地使用它。
該屬性在集合標簽(如“ set”)上設置,表示多對一擁有關聯,并負責所有數據庫的插入/更新/刪除。 它使關聯成為孩子的一部分。 它將保存外鍵的數據庫更新,因為它將在插入子代時直接發生。
尤其是在使用“集合”作為映射類型時,它可以提高性能,因為不需要將子級添加到父級集合中,這樣可以節省整個集合的負載。 那就是:由于集合映射的性質,添加新子元素時必須始終加載整個集合,因為這是hibernate可以確保新條目不是重復項的唯一方法,這是JRE Set的功能接口。
如果它涉及一個組件集合(=僅包含純值類型的集合),則inverse = true會被忽略并且沒有意義,因為Hibernate對對象具有完全控制權,并將選擇執行其操作的最佳方法。
如果它涉及分離的DTO對象(不包含任何休眠對象),則休眠將刪除所有值類型子對象,然后插入它們,因為它不知道哪個對象是新對象或存在對象,因為它已完全分離。 Hibernate將其視為新集合。
懶惰的Set.getChilds()是邪惡的
使用getChilds()會返回一個Set并會延遲加載所有子項,請小心。
當您只想添加或刪除孩子時,請勿使用此功能
始終實現equals / hashcode
確保始終對Hibernate管理的每個對象實施equals / hashcode,即使它看起來并不重要。 對于值類型對象也是如此。 如果對象不包含作為equals / hashcode候選者的屬性,請使用代理密鑰,例如,由UUID組成。 Hibernate使用equals / hashcode找出數據庫中是否已存在對象。 如果它涉及到一個現有的對象,但是Hibernate認為它是一個新對象,因為equals / hashcode沒有正確實現,則Hibernate將執行插入操作,并可能刪除舊值。 特別是對于Set中的值類型而言,這一點很重要,必須進行測試,因為它可以節省數據庫流量。 想法:您正在向Hibernate提供更多知識,以便可以使用它來優化他的操作。
使用版本
始終將version屬性與實體或用作實體的值類型一起使用。
由于Hibernate使用此信息來發現它是否涉及新對象或現有對象,因此這將減少數據庫流量。 如果不存在此屬性,則必須命中數據庫以查找它是否涉及新對象或現有對象。
渴望獲取
默認情況下,非延遲集合(子項)是通過額外選擇查詢加載的,該查詢僅在從數據庫加載父項之后才執行。
通過啟用熱切獲取,可以通過加載集合映射標簽上的屬性“ fetch = join”來完成與加載父對象相同的查詢。 如果啟用,則通過左外部聯接加載子項。 測試這是否可以提高性能。 如果發生許多聯接,或者如果它涉及具有許多列的表,則性能將變差而不是變好。
在值類型子對象中使用代理鍵
Hibernate將在由所有非空列組成的父子關系的值類型子項中構造主鍵。 這可能會導致奇怪的主鍵組合,尤其是在涉及日期列時。 日期列不應該是主鍵的一部分,因為它的毫秒部分將導致幾乎絕不相同的主鍵。 這將導致奇怪的數據庫性能,并且可能導致性能下降。 為了改善這一點,我們在所有子值類型對象中使用代理鍵,這是唯一的非null屬性。 然后,Hibernate將構造一個由外鍵和代理鍵組成的主鍵,該主鍵是邏輯上的且性能良好。 請注意,代理鍵僅用于數據庫優化,不需要在可能包含業務邏輯的equals / hashcode中使用。
- Java最佳實踐–高性能序列化
- Java最佳實踐– Vector vs ArrayList vs HashSet
- Java最佳實踐–字符串性能和精確字符串匹配
- Java最佳實踐–隊列之戰和鏈接的ConcurrentHashMap
- Java最佳實踐– Char到Byte和Byte到Char的轉換
- 如何在不到1ms的延遲內完成100K TPS
- 提升您的休眠引擎
- Cajo,用Java完成分布式計算的最簡單方法
翻譯自: https://www.javacodegeeks.com/2011/02/hibernate-mapped-collections.html