Hibernate延遲加載技巧詳解。本站提示廣大學習愛好者:(Hibernate延遲加載技巧詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是Hibernate延遲加載技巧詳解正文
本文實例講述了Hibernate延遲加載技巧。分享給年夜家供年夜家參考,詳細以下:
Hibernae 的延遲加載是一個異常經常使用的技巧,實體的聚集屬性默許會被延遲加載,實體所聯系關系的實體默許也會被延遲加載。Hibernate 經由過程這類延遲加載來下降體系的內存開支,從而包管 Hibernate 的運轉機能。
上面先來分析 Hibernate 延遲加載的“機密”。
聚集屬性的延遲加載
當 Hibernate 從數據庫中初始化某個耐久化實體時,該實體的聚集屬性能否隨耐久化類一路初始化呢?假如聚集屬性裡包括十萬,乃至百萬的記載,在初始化耐久化實體的同時,完成一切聚集屬性的抓取,將招致機能急劇降低。完整有能夠體系只須要應用耐久化類聚集屬性中的部門記載,而完整不是聚集屬性的全體,如許,沒有需要一次加載一切的聚集屬性。
關於聚集屬性,平日推舉應用延遲加載戰略。所謂延遲加載就是等體系須要應用聚集屬性時才從數據庫裝載聯系關系的數據。
例以下面 Person 類持有一個聚集屬性,該聚集屬性裡的元素的類型為 Address,該 Person 類的代碼片斷以下:
清單 1. Person.java
public class Person { // 標識屬性 private Integer id; // Person 的 name 屬性 private String name; // 保存 Person 的 age 屬性 private int age; // 應用 Set 來保留聚集屬性 private Set<Address> addresses = new HashSet<Address>(); // 上面省略了各屬性的 setter 和 getter 辦法 ... }
為了讓 Hibernate 能治理該耐久化類的聚集屬性,法式為該耐久化類供給以下映照文件:
清單 2. Person.hbm.xml
<?xml version="1.0" encoding="GBK"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.crazyit.app.domain"> <!-- 映照 Person 耐久化類 --> <class name="Person" table="person_inf"> <!-- 映照標識屬性 id --> <id name="id" column="person_id"> <!-- 界說主鍵生成器戰略 --> <generator class="identity"/> </id> <!-- 用於映照通俗屬性 --> <property name="name" type="string"/> <property name="age" type="int"/> <!-- 映照聚集屬性 --> <set name="addresses" table="person_address" lazy="true"> <!-- 指定聯系關系的外鍵列 --> <key column="person_id"/> <composite-element class="Address"> <!-- 映照通俗屬性 detail --> <property name="detail"/> <!-- 映照通俗屬性 zip --> <property name="zip"/> </composite-element> </set> </class> </hibernate-mapping>
從下面映照文件的代碼可以看出,Person 的聚集屬性中的 Address 類只是一個通俗的 POJO。該 Address 類裡包括 detail、zip 兩個屬性。因為 Address 類代碼異常簡略,故此處不再給出該類的代碼。
下面映照文件中 <set.../> 元素裡的代碼指定了 lazy="true"(關於 <set.../> 元從來說,lazy="true"是默許值),它指定 Hibernate 會延遲加載聚集屬性裡 Address 對象。
例如經由過程以下代碼來加載 ID 為 1 的 Person 實體:
Session session = sf.getCurrentSession(); Transaction tx = session.beginTransaction(); Person p = (Person) session.get(Person.class, 1); //<1> System.out.println(p.getName());
下面代碼只是須要拜訪 ID 為 1 的 Person 實體,其實不想拜訪這個 Person 實體所聯系關系的 Address 對象。此時有兩種情形:
1. 假如不延遲加載,Hibernate 就會在加載 Person 實體對應的數據記載時立刻抓取它聯系關系的 Address 對象。
2. 假如采取延遲加載,Hibernate 就只加載 Person 實體對應的數據記載。
很顯著,第二種做法既能削減與數據庫的交互,並且防止了裝載 Address 實體帶來的內存開支——這也是 Hibernate 默許啟用延遲加載的緣由。
如今的成績是,延遲加載究竟是若何完成的呢? Hibernate 在加載 Person 實體時,Person 實體的 addresses 屬性值是甚麼呢?
為懂得決這個成績,我們在 <1>號代碼處設置一個斷點,在 Eclipse 中停止 Debug,此時可以看到 Eclipse 的 Console 窗口有如圖 1 所示的輸入:
圖 1. 延遲加載聚集屬性的 Console 輸入
正如圖 1 輸入所看到的,此時 Hibernate 只從 Person 實體對應的數據表中抓取數據,並未從 Address 對象對應的數據表中抓取數據,這就是延遲加載。
那末 Person 實體的 addresses 屬性是甚麼呢?此時可以從 Eclipse 的 Variables 窗口看到如圖 2 所示的成果:
圖 2. 延遲加載的聚集屬性值
從圖 2 的方框裡的內容可以看出,這個 addresses 屬性其實不是我們熟習的 HashSet、TreeSet 等完成類,而是一個 PersistentSet 完成類,這是 Hibernate 為 Set 接口供給的一個完成類。
PersistentSet 聚集對象並未真正抓取底層數據表的數據,是以天然也沒法真正去初始化聚集裡的 Address 對象。不外 PersistentSet 聚集裡持有一個 session 屬性,這個 session 屬性就是 Hibernate Session,當法式須要拜訪 PersistentSet 聚集元素時,PersistentSet 就會應用這個 session 屬性去抓取現實的 Address 對象對應的數據記載。
那末究竟抓取那些 Address 實體對應的數據記載呢?這也難不倒 PersistentSet,由於 PersistentSet 聚集裡還有一個 owner 屬性,該屬性就解釋了 Address 對象所屬的 Person 實體,Hibernate 就會去查找 Address 對應數據表中外鍵值參照到該 Person 實體的數據。
例如我們單擊圖 2 所示窗口中 addresses 行,也就是告知 Eclipse 要調試、輸入 addresses 屬性,這就是要拜訪 addresses 屬性了,此時便可以在 Eclipse 的 Console 窗口看到輸入以下 SQL 語句:
select addresses0_.person_id as person1_0_0_, addresses0_.detail as detail0_, addresses0_.zip as zip0_ from person_address addresses0_ where addresses0_.person_id=?
這就是 PersistentSet 聚集跟據 owner 屬性去抓取特定 Address 記載的 SQL 語句。此時可以從 Eclipse 的 Variables 窗口看到圖 3 所示的輸入:
圖 3. 已加載的聚集屬性值
從圖 3 可以看出,此時的 addresses 屬性曾經被初始化了,聚集裡包括了 2 個 Address 對象,這恰是 Person 實體所聯系關系的兩個 Address 對象。
經由過程下面引見可以看出,Hibernate 關於 Set 屬性延遲加載症結就在於 PersistentSet 完成類。在延遲加載時,開端 PersistentSet 聚集裡其實不持有任何元素。但 PersistentSet 會持有一個 Hibernate Session,它可以包管當法式須要拜訪該聚集時“立刻”去加載數據記載,並裝入聚集元素。
與 PersistentSet 完成類相似的是,Hibernate 還供給了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等完成類,它們的功效與 PersistentSet 的功效年夜致相似。
熟習 Hibernate 聚集屬性讀者應當記得:Hibernate 請求聲明聚集屬性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不克不及用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等完成類,其緣由就是由於 Hibernate 須要對聚集屬性停止延遲加載,而 Hibernate 的延遲加載是依附 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 來完成的——也就是說,Hibernate 底層須要應用本身的聚集完成類來完成延遲加載,是以它請求開辟者必需用聚集接口、而不是聚集完成類來聲明聚集屬性。
Hibernate 對聚集屬性默許采取延遲加載,在某些特別的情形下,為 <set.../>、<list.../>、<map.../> 等元素設置 lazy="false"屬性來撤消延遲加載。
聯系關系實體的延遲加載
默許情形下,Hibernate 也會采取延遲加載來加載聯系關系實體,不論是一對多聯系關系、照樣一對一聯系關系、多對多聯系關系,Hibernate 默許都邑采取延遲加載。
關於聯系關系實體,可以將其分為兩種情形:
1. 聯系關系實體是多個實體時(包含一對多、多對多):此時聯系關系實體將以聚集的情勢存在,Hibernate 將應用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等聚集來治理延遲加載的實體。這就是後面所引見的情況。
2. 聯系關系實體是單個實體時(包含一對1、多對一):當 Hibernate 加載某個實體時,延遲的聯系關系實體將是一個靜態生成署理對象。
當聯系關系實體是單個實體時,也就是應用 <many-to-one.../> 或 <one-to-one.../> 映照聯系關系實體的情況,這兩個元素也可經由過程 lazy 屬性來指定延遲加載。
上面例子把 Address 類也映照成耐久化類,此時 Address 類也釀成實體類,Person 實體與 Address 實體構成一對多的雙向聯系關系。此時的映照文件代碼以下:
清單 3. Person.hbm.xml
<?xml version="1.0" encoding="GBK"?> <!-- 指定 Hibernate 的 DTD 信息 --> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="org.crazyit.app.domain"> <!-- 映照 Person 耐久化類 --> <class name="Person" table="person_inf"> <!-- 映照標識屬性 id --> <id name="id" column="person_id"> <!-- 界說主鍵生成器戰略 --> <generator class="identity"/> </id> <!-- 用於映照通俗屬性 --> <property name="name" type="string"/> <property name="age" type="int"/> <!-- 映照聚集屬性,聚集元素是其他耐久化實體 沒有指定 cascade 屬性,指定不掌握聯系關系關系 --> <set name="addresses" inverse="true"> <!-- 指定聯系關系的外鍵列 --> <key column="person_id"/> <!-- 用以映照到聯系關系類屬性 --> <one-to-many class="Address"/> </set> </class> <!-- 映照 Address 耐久化類 --> <class name="Address" table="address_inf"> <!-- 映照標識屬性 addressId --> <id name="addressId" column="address_id"> <!-- 指定主鍵生成器戰略 --> <generator class="identity"/> </id> <!-- 映照通俗屬性 detail --> <property name="detail"/> <!-- 映照通俗屬性 zip --> <property name="zip"/> <!-- 必需指定列名為 person_id, 與聯系關系實體中 key 元素的 column 屬性值雷同 --> <many-to-one name="person" class="Person" column="person_id" not-null="true"/> </class> </hibernate-mapping>
接上去法式經由過程以下代碼片斷來加載 ID 為 1 的 Person 實體:
// 翻開高低文相干的 Session Session session = sf.getCurrentSession(); Transaction tx = session.beginTransaction(); Address address = (Address) session.get(Address.class , 1); //<1> System.out.println(address.getDetail());
為了看到 Hibernate 加載 Address 實體時對其聯系關系實體的處置,我們在 <1>號代碼處設置一個斷點,在 Eclipse 中停止 Debug,此時可以看到 Eclipse 的 Console 窗口輸入以下 SQL 語句:
select address0_.address_id as address1_1_0_, address0_.detail as detail1_0_, address0_.zip as zip1_0_, address0_.person_id as person4_1_0_ from address_inf address0_ where address0_.address_id=?
從這條 SQL 語句不好看出,Hibernate 加載 Address 實體對應的數據表抓取記載,並未從 Person 實體對應的數據表中抓取記載,這是延遲加載施展了感化。
從 Eclipse 的 Variables 窗口看到如圖 4 所示的輸入:
圖 4. 延遲加載的實體
從圖 4 可以清晰地看到,此時 Address 實體所聯系關系的 Person 實體其實不是 Person 對象,而是一個 Person_$$_javassist_0 類的實例,這個類是 Hibernate 應用 Javassist 項目靜態生成的署理類——當 Hibernate 延遲加載聯系關系實體時,將會采取 Javassist 生成一個靜態署理對象,這個署理對象將擔任署理“暫未加載”的聯系關系實體。
只需運用法式須要應用“暫未加載”的聯系關系實體,Person_$$_javassist_0 署理對象會擔任去加載真實的聯系關系實體,並前往現實的聯系關系實體——這就是最典范的署理形式。
單擊圖 4 所示 Variables 窗口中的 person 屬性(也就是在調試形式下強行應用 person 屬性),此時看到 Eclipse 的 Console 窗口輸入以下的 SQL 語句:
select person0_.person_id as person1_0_0_, person0_.name as name0_0_, person0_.age as age0_0_ from person_inf person0_ where person0_.person_id=?
下面 SQL 語句就是去抓取“延遲加載”的聯系關系實體的語句。此時可以看到 Variables 窗口輸入圖 5 所示的成果:
圖 5. 已加載的實體
Hibernate 采取“延遲加載”治理聯系關系實體的形式,其實就在加載主實體時,並未真正去抓取聯系關系實體對應數據,而只是靜態地生成一個對象作為聯系關系實體的署理。當運用法式真正須要應用聯系關系實體時,署理對象會擔任從底層數據庫抓取記載,並初始化真實的聯系關系實體。
在 Hibernate 的延遲加載中,客戶端法式開端獲得的只是一個靜態生成的署理對象,而真實的實體則拜托給署理對象來治理——這就是典范的署理形式。
署理形式
署理形式是一種運用異常普遍的設計形式,當客戶端代碼須要挪用某個對象時,客戶端現實上也不關懷能否精確獲得該對象,它只需一個能供給該功效的對象便可,此時我們便可前往該對象的署理(Proxy)。
在這類設計方法下,體系會為某個對象供給一個署理對象,並由署理對象掌握對源對象的援用。署理就是一個 Java 對象代表另外一個 Java 對象來采用行為。在某些情形下,客戶端代碼不想或不克不及夠直接挪用被挪用者,署理對象可以在客戶和目的對象之間起到中介的感化。
對客戶端而言,它不克不及分辯出署理對象與真實對象的差別,它也不必分辯署理對象和真實對象的差別。客戶端代碼其實不曉得真實的被署理對象,客戶端代碼面向接口編程,它僅僅持有一個被署理對象的接口。
總而言之,只需客戶端代碼不克不及或不想直接拜訪被挪用對象——這類情形有許多緣由,好比須要創立一個體系開支很年夜的對象,或許被挪用對象在長途主機上,或許目的對象的功效還缺乏以知足需求……,而是額定創立一個署理對象前往給客戶端應用,那末這類設計方法就是署理形式。
上面示范一個簡略的署理形式,法式起首供給了一個 Image 接口,代表年夜圖片對象所完成的接口,該接口代碼以下:
清單 3. Image.java
public interface Image { void show(); }
該接口供給了一個完成類,該完成類模仿了一個年夜圖片對象,該完成類的結構器應用 Thread.sleep() 辦法來暫停 3s。上面是該 BigImage 的法式代碼。
清單 4. BigImage.java
// 應用該 BigImage 模仿一個很年夜圖片 public class BigImage implements Image { public BigImage() { try { // 法式暫停 3s 形式模仿體系開支 Thread.sleep(3000); System.out.println("圖片裝載勝利 ..."); } catch (InterruptedException ex) { ex.printStackTrace(); } } // 完成 Image 裡的 show() 辦法 public void show() { System.out.println("繪制現實的年夜圖片"); } }
下面的法式代碼暫停了 3s,這注解創立一個 BigImage 對象須要 3s 的時光開支——法式應用這類延遲來模仿裝載此圖片所招致的體系開支。假如不采取署理形式,當法式中創立 BigImage 時,體系將會發生 3s 的延遲。為了不這類延遲,法式為 BigImage 對象供給一個署理對象,BigImage 類的署理類以下所示。
清單 5. ImageProxy.java
public class ImageProxy implements Image { // 組合一個 image 實例,作為被署理的對象 private Image image; // 應用籠統實體來初始化署理對象 public ImageProxy(Image image) { this.image = image; } /** * 重寫 Image 接口的 show() 辦法 * 該辦法用於掌握對被署理對象的拜訪, * 並依據須要擔任創立和刪除被署理對象 */ public void show() { // 只要認真正須要挪用 image 的 show 辦法時才創立被署理對象 if (image == null) { image = new BigImage(); } image.show(); } }
下面的 ImageProxy 署理類完成了與 BigImage 雷同的 show() 辦法,這使得客戶端代碼獲得到該署理對象以後,可以將該署理對象當做 BigImage 來應用。
在 ImageProxy 類的 show() 辦法中增長了掌握邏輯,這段掌握邏輯用於掌握當體系真正挪用 image 的 show() 時,才會真正創立被署理的 BigImage 對象。上面法式須要應用 BigImage 對象,但法式其實不是直接前往 BigImage 實例,而是先前往 BigImage 的署理對象,以下面法式所示。
清單 6. BigImageTest.java
public class BigImageTest { public static void main(String[] args) { long start = System.currentTimeMillis(); // 法式前往一個 Image 對象,該對象只是 BigImage 的署理對象 Image image = new ImageProxy(null); System.out.println("體系獲得 Image 對象的時光開支 :" + (System.currentTimeMillis() - start)); // 只要當現實挪用 image 署理的 show() 辦法時,法式才會真正創立被署理對象。 image.show(); } }
下面法式初始化 image 異常快,由於法式並未真正創立 BigImage 對象,只是獲得了 ImageProxy 署理對象——直到法式挪用 image.show() 辦法時,法式須要真正挪用 BigImage 對象的 show() 辦法,法式此時才真正創立 BigImage 對象。運轉下面法式,看到如圖 6 所示的成果。
圖 6. 應用署理形式進步機能
看到如圖 6 所示的運轉成果,讀者應當能認同:應用署理形式進步了獲得 Image 對象的體系機能。但能夠有讀者會提出疑問:法式挪用 ImageProxy 對象的 show() 辦法時一樣須要創立 BigImage 對象啊,體系開支並未真正削減啊?只是這類體系開支延遲了罷了啊?
我們可以從以下兩個角度往返答這個成績:
把創立 BigImage 推延到真正須要它時才創立,如許能包管後面法式運轉的流利性,並且能削減 BigImage 在內存中的存活時光,從微觀上節儉了體系的內存開支。
有些情形下,或許法式永久不會真正挪用 ImageProxy 對象的 show() 辦法——意味著體系基本不必創立 BigImage 對象。在這類情況下,應用署理形式可以明顯地進步體系運轉機能。
與此完整相似的是,Hibernate 也是經由過程署理形式來“推延”加載聯系關系實體的時光,假如法式其實不須要拜訪聯系關系實體,那法式就不會去抓取聯系關系實體了,如許既可以節儉體系的內存開支,也能夠延長 Hibernate 加載實體的時光。
小結
Hibernate 的延遲加載(lazy load)實質上就是署理形式的運用,我們在曩昔的歲月裡就常常經由過程署理形式來下降體系的內存開支、晉升運用的運轉機能。Hibernate 充足應用了署理形式的這類優勢,並聯合了 Javassist 或 CGLIB 來靜態地生成署理對象,這加倍增長了署理形式的靈巧性,Hibernate 給這類用法一個新稱號:延遲加載。不管如何,充足剖析、懂得這些開源框架的完成可以更好的感觸感染經典設計形式的優勢地點。
願望本文所述對年夜家基於Hibernate框架的Java法式設計有所贊助。