標籤:sse iter opera 中間 java 2.3 讀取 edm rdo
1 消極式載入策略
Hibernate 的消極式載入(lazy load)是一個被廣泛使用的技術。這種消極式載入保證了應用只有在需要時才去資料庫中抓取相應的記錄。通過消極式載入技術可以避免過多、過早地載入資料表裡的資料,從而降低應用的記憶體開銷。Hibernate 的消極式載入本質上就是代理模式的應用,當程式通過 Hibernate 裝載一個實體時,預設情況下,Hibernate 並不會立即抓取它的集合屬性、關聯實體所以對應的記錄,而是通過產生一個代理來表示這些集合屬性、關聯實體,這就是代理模式應用帶來的優勢。
但是,消極式載入也是項目開發中特別常見的一個錯誤。如果對一個類或者集合配置了延遲檢索策略,那麼必須當代理類執行個體或代理集合處於持久化狀態(即處於Session範圍內)時,才能初始化它。如果在游離狀態時才初始化它,就會產生延遲初始化錯誤。所以,在開發獨立的DAO資料訪問層時應該格外小心這個問題。
如果在擷取對象的時候使用的是session.get()是不會消極式載入的,只有在使用load、hql時候才會消極式載入。
Hibernate中允許使用消極式載入的地方主要有以下幾個地方:
<hibernate-mapping default-lazy=(true|false)”true”>:設定全域的消極式載入策略。
<class lazy=(true|false)>:DTD沒設定預設值,推理預設值為true
<property lazy=(true|false)>:設定欄位消極式載入,預設為false
<component lazy=(true|false):預設為false
<subclass lazy=(true|false)>:預設設定為true
<join-subclass lazy=(true|false)>:預設設定為true
<union-subclass lazy=(true|false)>:預設設定為true
<many-to-one lazy=(proxy|no-proxy|false)>:預設為proxy
<one-to-one lazy=(proxy|no-proxy|false)>:預設為proxy
<map lazy=(true|extra|false)>:預設為true
<set lazy=(true|extra|false)>:預設為true
<bag lazy=(true|extra|false)>:預設為true
<ibag lazy=(true|extra|false)>:預設為true
<list lazy=(true|extra|false)>:預設為true
2 對象載入<class>2.1 消極式載入策略(預設)
如果想對實體物件使用消極式載入,必須要在實體的映射設定檔中進行相應的配置
<class name="Person" table="PERSON" lazy="true">
1 tx = session.beginTransaction();2 Person p=(Person) session.load(Person.class, "001");//(1)3 System.out.println("0: "+p.getPersonId());//(2)4 System.out.println("0: "+p.getName());//(3)5 tx.commit();6 session.close();
執行到(1)並沒有出現sql語句,並沒有從資料庫中抓取資料。這個時候查看記憶體對象p如下:
觀察person對象,我們可發現是Person$$EnhancerBy..的類型的對象。這裡所返回的物件類型就是Person對象的代理對象,在hibernate中通過使用CGLB來先動態構造一個目標對象的代理類對象,並且在代理對象中包含目標對象的所有屬性和方法。所以,對於用戶端而言是否為代理類是無關緊要的,對他來說是透明的。這個對象中,僅僅設定了id屬性(即personId的值),這是為了便於後面根據這個Id從資料庫中來擷取資料。
運行到(2)處,輸出為001,但是仍然沒有從資料庫裡面讀取資料。這個時候代理類的作用就體現出來了,用戶端覺得person類已經實現了(事實上並未建立)。但是,如果這個會後session關閉,再使用person對象就會出錯了。
調試運行到(3)處,要用到name屬性,但是這個值在資料庫中。所以hibernate從資料庫裡面抓取了資料,sql語句如下所示:
Hibernate: select person0_.PERSONID as PERSONID3_0_, person0_.NAME as NAME3_0_ from PERSON person0_ where person0_.PERSONID=?
這時候,我們查看記憶體裡面的對象如下:
真正的Person對象放在CGLIB$CALLBACK_0對象中的target屬性裡。
這樣,通過一個中間代理對象,Hibernate實現了實體的消極式載入,只有當使用者真正發起獲得實體物件屬性的動作時,才真正會發起資料庫查詢操作。所以實體的消極式載入是用通過中間代理類完成的,所以只有session.load()方法才會利用實體消極式載入,因為只有session.load()方法才會返回實體類的代理類對象。
2.2 非消極式載入策略
Hibernate預設的策略便是非消極式載入的,所以設定lazy=false
1 tx = session.beginTransaction();2 Person p=(Person) session.load(Person.class, "001");//(1)3 System.out.println("0: "+p.getPersonId());//(2)4 System.out.println("0: "+p.getName());//(3)5 tx.commit();6 session.close();
調試運行到(1)處時,hibernate直接執行如下sql語句:
Hibernate: select person0_.PERSONID as PERSONID3_0_, person0_.NAME as NAME3_0_ from PERSON person0_ where person0_.PERSONID=?
我們在查看記憶體快照如下:
這個時候就不是一個代理類了,而是Person對象本身了。裡面的屬性也已經全部普通屬性也全部被載入。這裡說普通屬性是因為addresses這個集合對象並沒有被載入,因為set自己本身也可以設定lazy屬性。所以,這裡也反映出class對象的lazy並不能控制關聯或集合的載入策略。
2.3 總結
Hibernate中<class lazy=””>預設為true。如果,在load的時候只會返回一個代理類,並不會正在從資料庫中讀取資料。第一次用到時,會將所有普通屬性(set這種就不是)全部載入進來。如果第一次使用到時,session已經關閉將發生錯誤。
如果顯式是設定lazy=false,load的時候即會把所有普通屬性全部讀取進來。而且,返回的將是一個真正的該類型的對象(如Person),而不是代理類。
3 欄位載入(property)
在Hibernate3中,引入了一種新的特性——屬性的消極式載入,這個機制又為擷取高效能查詢提供了有力的工具。在大資料對象讀取時,如Person對象中有一個School欄位,該欄位是一個java.sql.Clob類型,包含了使用者的簡曆資訊,當我們載入該對象時,我們不得不每一次都要載入這個欄位,而不論我們是否真的需要它,而且這種大資料對象的讀取本身會帶來很大的效能開銷。
3.1 消極式載入
1、 <class lazy=”false”>
配置如下
1 tx = session.beginTransaction();2 Person p=(Person) session.load(Person.class, "001");//(1)3 System.out.println("");//(2)4 System.out.println("0: "+p.getPersonId());//(3)5 System.out.println("0: "+p.getName());//(4)6 System.out.println("0: "+p.getSchool());//(5)7 tx.commit();
1 <property name="name" type="java.lang.String">2 <column name="NAME" />3 </property>4 <property name="school" type="java.lang.String" lazy="true">5 <column name="SCHOOL"></column>6 </property>
當運行到p的時候,全部載入了,執行語句如下:
Hibernate:
select
person0_.PERSONID as PERSONID3_0_,
person0_.NAME as NAME3_0_,
person0_.SCHOOL as SCHOOL3_0_
from
PERSON person0_
where
person0_.PERSONID=?
所有普通屬性都均已載入。
2、<class lazy=”true”>
School的lazy屬性自然還是true。當程式運行到(4)時,也同樣載入了全部屬性,執行了如下sql:
Hibernate: select person0_.PERSONID as PERSONID3_0_, person0_.NAME as NAME3_0_, person0_.SCHOOL as SCHOOL3_0_ from PERSON person0_ where person0_.PERSONID=?
結果就是無效,不管採用何種策略都是無效的,和我們想想的有較大出路。下面是一段來自hibernate官方文檔的話。
Lazy property loading requires buildtime bytecode instrumentation. If your persistent classes are not enhanced, Hibernate will ignore lazy property settings and return to immediate fetching.
應該是因為,我們並未用到編譯時間位元組碼增強技術的原因。如果只對部分property進行消極式載入的話,hibernate還提供了另外的方式,也是更為推薦的方式,即HQL或者條件查詢。
A different way of avoiding unnecessary column reads, at least for read-only transactions, is to use the projection features of HQL or Criteria queries. This avoids the need for buildtime bytecode processing and is certainly a preferred solution.
4 集合無關聯
Person類
1 public class Person {2 private String name;3 private String sex;4 private Set<String> addresses;5 }
Person.hbm.xml
1 <class name="com.hbm.hibernate.Person" table="PERSON"> 2 <id name="name" type="java.lang.String"> 3 <column name="NAME"/> 4 <generator class="assigned"/> 5 </id> 6 <property name="sex" type="java.lang.String"> 7 <column name="SEX"/> 8 </property> 9 <set name="addresses" table="ADDRESSES" inverse="false" lazy="true" fetch="join">10 <key column="NAME"/>11 <element column="ADDRESS" type="java.lang.String"></element>12 </set>13 </class>
4.1 非消極式載入策略
對應檔的配置<set lazy=”false”>。
1 tx = session.beginTransaction();2 Person person=(Person) session.load(Person.class, "XiJinping");//(1)3 System.out.println("");//(2)4 System.out.println("0: "+person.getName());//(3)5 System.out.println("1: "+person.getSex());//(4)6 System.out.println("2: "+person.getAddresses());//(5)7 tx.commit();
運行到(4)處時,載入了全部屬性,執行了如下sql語句。
1 Hibernate: 2 /* load com.hbm.hibernate.Person */ select 3 person0_.NAME as NAME0_0_, 4 person0_.SEX as SEX0_0_ 5 from 6 PERSON person0_ 7 where 8 person0_.NAME=? 9 Hibernate: 10 /* load collection com.hbm.hibernate.Person.addresses */ select11 addresses0_.NAME as NAME0_,12 addresses0_.ADDRESS as ADDRESS0_ 13 from14 ADDRESSES addresses0_ 15 where16 addresses0_.NAME=?
fetch策略的配合使用,當<set lazy=”false” fetch=”join”>時,執行的sql語句如下。這個是有,將不再採用兩條select語句的方式,而是採用左串連的方式進行,有利於提高效率。
Hibernate: /* load com.hbm.hibernate.Person */ select person0_.NAME as NAME0_0_, person0_.SEX as SEX0_0_, addresses1_.NAME as NAME2_, addresses1_.ADDRESS as ADDRESS2_ from PERSON person0_ left outer join ADDRESSES addresses1_ on person0_.NAME=addresses1_.NAME where person0_.NAME=?
4.2 消極式載入策略
對應檔的配置<set lazy=”true”>。
當程式運行到(4),hibernate載入了Person對象的其他全部屬性,執行了如下sql語句。
Hibernate: /* load com.hbm.hibernate.Person */ select person0_.NAME as NAME0_0_, person0_.SEX as SEX0_0_ from PERSON person0_ where person0_.NAME=?
當程式運行到(5)時,hibernate載入了所有的address對象,執行如下sql語句
1 Hibernate: 2 /* load collection com.hbm.hibernate.Person.addresses */ select3 addresses0_.NAME as NAME0_,4 addresses0_.ADDRESS as ADDRESS0_ 5 from6 ADDRESSES addresses0_ 7 where8 addresses0_.NAME=?
4.2 消極式載入extra
It can also be used to enable "extra-lazy" fetching where most operations do not initialize the collection. This is suitable for large collections.
大部分操作的時候並不會載入集合,適用於大的集合。extra其實是一種比較智能的消極式載入,即調用集合的size/contains等方法的時候,hibernate並不會去載入整個集合的資料,而是發出一條聰明的SQL語句,以便獲得需要的值,只有在真正需要用到這些集合元素對象資料的時候,才去發出查詢語句載入所有對象的資料。
對應檔配置對應檔的配置<set lazy=”extra”>
1 public int getNum(){ 2 return addresses.size(); 3 } 4 5 tx = session.beginTransaction(); 6 Person person=(Person) session.load(Person.class, "XiJinping");//(1) 7 System.out.println("");//(2) 8 System.out.println("0: "+person.getName());//(3) 9 System.out.println("1: "+person.getSex());//(4)10 System.out.println("2: "+person.getNum());//(5)11 System.out.println("3: "+person.getAddresses());//(6)12 tx.commit();
當程式運行到(4)時,進行了第一次的載入,載入了person對象的所有普通屬性,執行sql如下:
Hibernate: /* load com.hbm.hibernate.Person */ select person0_.NAME as NAME0_0_, person0_.SEX as SEX0_0_ from PERSON person0_ where person0_.NAME=?
當程式運行到(5)時,進行了第二次載入,這個時候並沒有去載入set集合中的所有屬性,hibernate智能的用sql語句擷取了集合中的數量,執行的sql語句如下:
Hibernate: select count(ADDRESS) from ADDRESSES where NAME =?
當程式運行到(6)時,進行了第三次載入,將集合中的所有對象均載入進來了,執行的sql語句如下:
Hibernate: /* load collection com.hbm.hibernate.Person.addresses */ select addresses0_.NAME as NAME0_, addresses0_.ADDRESS as ADDRESS0_ from ADDRESSES addresses0_ where addresses0_.NAME=?
4.4 總結
在集合的3中消極式載入中,我覺得最有的配置應該是extra。但是,預設配置false和extra均不適用於,session會話之外的情況。
Hibernate中集合屬性的消極式載入應該來說是最為重要的,因為如果集合屬性裡麵包含十萬百萬記錄,在初始化持久實體的同時,完成所有集合屬性的抓取,將導致效能急劇下降。
5 集合有關聯
Person類
1 public class Person {2 private String personId;3 private String name;4 private Set addresses;5 public int getNum(){6 return addresses.size();7 }8 }
Address類
1 public class Address {2 private String addressId;3 private String addressDetail;4 private Set people;5 }
Person.hbm.xml
<class name="com.hbm.hibernate.Person" table="PERSON">……… <set name="addresses" table="PERSON_ADDRESS" cascade="all"> <key> <column name="PERSONID" /> </key> <many-to-many class="com.hbm.hibernate.Address" column="ADDRESSID"></many-to-many> </set> </class>
5.1 非消極式載入
對應檔配置<set lazy=”false”>
1 tx = session.beginTransaction();2 Person person=(Person) session.load(Person.class, "001");//(1)3 System.out.println("");//(2)4 System.out.println("0: "+person.getPersonId());//(3)5 System.out.println("1: "+person.getName());//(4)6 System.out.println("2: "+person.getNum());//(5)7 System.out.println("3: "+person.getAddresses());//(6)8 tx.commit();
當程式運行到(4)時,hibernate載入了所有屬性,執行的sql語句如下:
Hibernate: select person0_.PERSONID as PERSONID2_0_, person0_.NAME as NAME2_0_ from PERSON person0_ where person0_.PERSONID=?Hibernate: select addresses0_.PERSONID as PERSONID1_, addresses0_.ADDRESSID as ADDRESSID1_, address1_.ADDRESSID as ADDRESSID0_0_, address1_.ADDRESSDETAIL as ADDRESSD2_0_0_ from PERSON_ADDRESS addresses0_ left outer join ADDRESS address1_ on addresses0_.ADDRESSID=address1_.ADDRESSID where addresses0_.PERSONID=?
5.2 消極式載入與extra策略
與無關聯關係時一致,不再累述。
6 1-1和N-1消極式載入策略
LineItem類
public class LineItem { private int lineNumber; private int amount; private double price;private Product product;}
Product類
public class Product { private String id; private String name;private double listprice;}
LineItem.hbm.xml
<class name="com.hbm.hibernate.LineItem" table="LINEITEM"> <id name="lineNumber" type="int"> <column name="LINENUMBER" /> <generator class="assigned" /> </id> <property name="amount" type="int"> <column name="AMOUNT" /> </property> <property name="price" type="double"> <column name="PRICE" /> </property> <join table="LINE_PRODUCT"> <key column="LINENUMBER"/> <many-to-one name="product" unique="true" lazy="false" not-null="true" column="PRODUCTID"/> </join> </class>
6.1 非消極式載入
對應檔配置<many-to-one lazy=”false”>
1 tx = session.beginTransaction();2 LineItem l=(LineItem) session.load(LineItem.class, 2);//(1)3 System.out.println("");//(2)4 System.out.println("0: "+l.getLineNumber());//(3)5 System.out.println("1: "+l.getAmount());//(4)6 System.out.println("2: "+l.getProduct());//(5)7 tx.commit();
程式運行到(4)處時,hibernate載入了所有屬性,執行了如下sql語句:
Hibernate: select lineitem0_.LINENUMBER as LINENUMBER1_0_, lineitem0_.AMOUNT as AMOUNT1_0_, lineitem0_.PRICE as PRICE1_0_, lineitem0_1_.PRODUCTID as PRODUCTID2_0_ from LINEITEM lineitem0_ inner join LINE_PRODUCT lineitem0_1_ on lineitem0_.LINENUMBER=lineitem0_1_.LINENUMBER where lineitem0_.LINENUMBER=?Hibernate: select product0_.PRODUCTID as PRODUCTID0_0_, product0_.NAME as NAME0_0_, product0_.LISTPRICE as LISTPRICE0_0_ from PRODUCT product0_ where product0_.PRODUCTID=?
在這個時候,去查看記憶體中的LineItem類型對象,我們發現也是一個代理類。而回呼函數中,tagert屬性中的Prdouct是一個真正的Product類型對象。
6.2 消極式載入proxy
對應檔設定<many-to-one lazy=”proxy”>
當程式運行到(4)時,進行了第一次的載入,執行的sql語句如下:
Hibernate: select lineitem0_.LINENUMBER as LINENUMBER1_0_, lineitem0_.AMOUNT as AMOUNT1_0_, lineitem0_.PRICE as PRICE1_0_, lineitem0_1_.PRODUCTID as PRODUCTID2_0_ from LINEITEM lineitem0_ inner join LINE_PRODUCT lineitem0_1_ on lineitem0_.LINENUMBER=lineitem0_1_.LINENUMBER where lineitem0_.LINENUMBER=?
當程式運行到(5)時,進行了第二次的載入,執行的sql語句如下:
Hibernate: select product0_.PRODUCTID as PRODUCTID0_0_, product0_.NAME as NAME0_0_, product0_.LISTPRICE as LISTPRICE0_0_ from PRODUCT product0_ where product0_.PRODUCTID=?
這個時候,我們去參看記憶體,發現target中的product屬性便是個代理類,如所示:
6.3 總結
預設情況下,Hibernate 也會採用消極式載入來載入關聯實體,不管是一對多關聯、還是一對一關聯、多對多關聯,Hibernate 預設都會採用消極式載入。
對於關聯實體,可以將其分為兩種情況:
關聯實體是多個實體時(包括一對多、多對多):此時關聯實體將以集合的形式存在,Hibernate 將使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合來管理消極式載入的實體。這就是前面所介紹的情形。
關聯實體是單個實體時(包括一對一、多對一):當 Hibernate 載入某個實體時,延遲的關聯實體將是一個動態組建代理程式對象。
當關聯實體是單個實體時,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射關聯實體的情形,這兩個元素也可通過 lazy 屬性來指定消極式載入。
7 繼承(subclass為例)
Payment類
1 public class Payment {2 private long id;3 private long amount;4 }
CreditCardPayment類
public class CreditCardPayment extends Payment { private long creditId;private String cardType;}
creditCardPayment.hbm.xml
<subclass name="com.hbm.hibernate.CreditCardPayment" discriminator-value="CREDIT" extends="com.hbm.hibernate.Payment" lazy="false"> <property name="creditId" column="CREDITID" type="long"></property> <property name="cardType" column="CARDTYPE" type="java.lang.String"></property> </subclass>
8.1 非消極式載入
對應檔配置<subclass lazy=”false”>。
1 tx = session.beginTransaction();2 CreditCardPayment ccp=(CreditCardPayment) session.load(CreditCardPayment.class,new Long(8889));//(1)3 System.out.println("");//(2)4 System.out.println("0: "+ccp.getId());//(3)5 System.out.println("1: "+ccp.getAmount());//(4)6 System.out.println("2: "+ccp.getCardType());//(5)7 tx.commit();
程式運行到(1)時,載入全部屬性,執行的sql語句如下:
Hibernate: select creditcard0_.ID as ID0_0_, creditcard0_.AMOUNT as AMOUNT0_0_, creditcard0_.CREDITID as CREDITID0_0_, creditcard0_.CARDTYPE as CARDTYPE0_0_ from PAYMENT creditcard0_ where creditcard0_.ID=? and creditcard0_.PAYMENT_TYPE=‘CREDIT‘
7.2 消極式載入
對應檔配置<subclass lazy=”true”>
Hibernate: select creditcard0_.ID as ID0_0_, creditcard0_.AMOUNT as AMOUNT0_0_, creditcard0_.CREDITID as CREDITID0_0_, creditcard0_.CARDTYPE as CARDTYPE0_0_ from PAYMENT creditcard0_ where creditcard0_.ID=? and creditcard0_.PAYMENT_TYPE=‘CREDIT‘
程式執行到(4)時,第一次載入全部屬性,執行的sql語句如上。
Hibernate Lazy屬性