hibernate 關聯映射
《Hibernate 關系映射》是我很早之前收集、總結整理的,在此也發上來 希望對大家有用。因為是很早之前寫的,不當之處請指正。
一、概念:
關系:名詞,事物之間互相作用、互相聯系的狀态。
關聯:名詞:表示對象(資料庫表)之間的關系;動詞:将對象(資料庫表)之間通過某種方式聯系起來。
映射:将一種形式轉化為另一種形式,包括關系。
級聯:動詞,有關系的雙方中操作一方,另一方也将采取一些動作。
值類型:對象不具備資料庫同一性,屬于一個實體執行個體其持久化狀态被嵌入到所擁有的實體的表行中,沒有辨別符。
實體類型:具有資料庫辨別符。
二、資料庫:
1、關系
2.1.1、一對一、一對多、多對多
2.1.2、如何表示? 外鍵+索引
2、級聯:
2.2.1、級聯删除
三、面向對象語言中(Java中):
3.1.1、一對一、一對多、多對多
3.1.2、如何表示? 執行個體變量(對象+集合)
3.2.1、級聯删除
3.2.2、級聯更新
3.2.3、級聯儲存
四、如何把資料庫關系表示為面向對象中的關系:
1、關聯:将資料庫表之間的關系轉化為對象之間的關系;在Hibernate中總指實體之間的關系。
2、映射:完成java對象到資料庫表的雙向轉換。
3、級聯(可選):将資料庫中的級聯轉化為對象中的級聯(兩者(對象和資料庫表)沒關系)。
4、Hibernate的表和對象的映射:
1、實體類型映射:
4.1.1、主鍵之間的映射
4.1.2、類屬性與表字段的映射
4.1.3、元件映射
4.1.4、集合映射
2、實體關聯關系映射:
4.2.1、關聯關系映射
五、Hibernate映射示例:
5.1、實作
5.1.1、資料庫表定義(主表)
5.1.1.1、使用者表
Java代碼
- CREATE TABLE TBL_USER (
- UUID NUMBER(10) NOT NULL,
- NAME VARCHAR2(100),
- AGE NUMBER(10) NOT NULL,
- PROVINCE VARCHAR2(100),
- CITY VARCHAR2(100),
- STREET VARCHAR2(100),
- CONSTRAINT PK_USER PRIMARY KEY(UUID));
CREATE TABLE TBL_USER (UUID NUMBER(10) NOT NULL, NAME VARCHAR2(100),AGE NUMBER(10) NOT NULL, PROVINCE VARCHAR2(100),CITY VARCHAR2(100),STREET VARCHAR2(100),CONSTRAINT PK_USER PRIMARY KEY(UUID));
5.1.1.2、使用者普通資訊表(一個使用者有一個資料)
- CREATE TABLE TBL_USER_GENERAL (
- UUID NUMBER(10) NOT NULL,
- REALNAME VARCHAR2(10),
- GENDER VARCHAR2(10),
- BIRTHDAY NUMBER(10),
- HEIGHT NUMBER(10),
- WEIGHT NUMBER(10) ,
- CONSTRAINT PK_USER_GENERAL PRIMARY KEY(UUID),
- CONSTRAINT FK_USER_GENERAL FOREIGN KEY(UUID)
- REFERENCES TBL_USER(UUID));
CREATE TABLE TBL_USER_GENERAL (UUID NUMBER(10) NOT NULL,REALNAME VARCHAR2(10),GENDER VARCHAR2(10),BIRTHDAY NUMBER(10),HEIGHT NUMBER(10),WEIGHT NUMBER(10) , CONSTRAINT PK_USER_GENERAL PRIMARY KEY(UUID), CONSTRAINT FK_USER_GENERAL FOREIGN KEY(UUID) REFERENCES TBL_USER(UUID));
5.1.1.3、農場表(一個使用者有多個農場)
- CREATE TABLE TBL_FARM (
- NAME VARCHAR2(10),
- FK_USER_ID NUMBER(10),
- CONSTRAINT PK_FARM PRIMARY KEY(UUID),
- CONSTRAINT FK_USER_FARM FOREIGN KEY(FK_USER_ID)
- REFERENCES TBL_USER(UUID));
CREATE TABLE TBL_FARM (UUID NUMBER(10) NOT NULL, NAME VARCHAR2(10), FK_USER_ID NUMBER(10), CONSTRAINT PK_FARM PRIMARY KEY(UUID), CONSTRAINT FK_USER_FARM FOREIGN KEY(FK_USER_ID) REFERENCES TBL_USER(UUID));
5.1.2、對象定義
5.1.2.1、使用者位址Model
- package cn.javass.h3test.model;
- public class AddressModel implements java.io.Serializable {
- private String province;//省
- private String city;//市
- private String street;//街道
- }
package cn.javass.h3test.model;public class AddressModel implements java.io.Serializable { private String province;//省 private String city;//市 private String street;//街道}
5.1.2.2、使用者Model
- import java.util.HashSet;
- import java.util.Set;
- public class UserModel implements java.io.Serializable {
- private int uuid;
- private String name;//名稱
- private int age;//年齡
- private AddressModel address;//位址
- private UserGeneralModel userGeneral;//使用者普通資訊
- private Set<FarmModel> farms = new HashSet<FarmModel>();//擁有的農場
package cn.javass.h3test.model;import java.util.HashSet;import java.util.Set;public class UserModel implements java.io.Serializable { private int uuid; private String name;//名稱 private int age;//年齡 private AddressModel address;//位址 private UserGeneralModel userGeneral;//使用者普通資訊 private Set<FarmModel> farms = new HashSet<FarmModel>();//擁有的農場}
5.1.2.3、使用者普通資訊Model
- public class UserGeneralModel implements java.io.Serializable {
- private String realname;//真實姓名
- private String gender;//性别
- private String birthday;//生日
- private int weight;//體重
- private int height;//身高
- private UserModel user;//所屬使用者
- }
package cn.javass.h3test.model;public class UserGeneralModel implements java.io.Serializable { private int uuid; private String realname;//真實姓名 private String gender;//性别 private String birthday;//生日 private int weight;//體重 private int height;//身高private UserModel user;//所屬使用者}
5.1.2.4、農場Model
- public class FarmModel implements java.io.Serializable {
- private String name;//農場的名稱
- private UserModel user;//所屬使用者
package cn.javass.h3test.model;public class FarmModel implements java.io.Serializable { private int uuid; private String name;//農場的名稱 private UserModel user;//所屬使用者}
5.2、配置
5.2.1、實體類型映射:
5.2.1.1、主鍵的映射(UserModel.hbm.xml)
- <id name="uuid">
- <generator class="sequence">
- <param name="sequence">user_uuid</param>
- </generator>
- </id>
<id name="uuid"><generator class="sequence"><param name="sequence">user_uuid</param></generator></id>
5.2.1.2、類屬性與表字段的映射(UserModel.hbm.xml)
- <property name="name"/>
<property name="name"/>
5.2.1.3、元件映射(UserModel.hbm.xml)
- <component name="address" class="cn.javass.h3test.model.AddressModel">
- <property name="province"/>
- <property name="city"/>
- <property name="street"/>
- </component>
<component name="address" class="cn.javass.h3test.model.AddressModel"> <property name="province"/> <property name="city"/> <property name="street"/></component>
5.2.1.4、集合映射(Set、List、Map) (都是通過外鍵連接配接的,,,預設延遲抓取)
Set:
- private Set<String> farmSet = new HashSet<String>();
private Set<String> farmSet = new HashSet<String>();
- <set name="farmSet" table="TBL_FARM" >
- <key column="fk_user_id"/><!—該外鍵是tbl_farm的-->
- <element type="string" column="name"/>
- </set>
<set name="farmSet" table="TBL_FARM" ><key column="fk_user_id"/><!—該外鍵是tbl_farm的--> <element type="string" column="name"/></set>
- private List<String> farmList = new ArrayList<String>();
private List<String> farmList = new ArrayList<String>();
- <list name="farmList" table="TBL_FARM">
- <key column="fk_user_id"/>
- <list-index column="uuid"></list-index>
- </list>
<list name="farmList" table="TBL_FARM"> <key column="fk_user_id"/> <list-index column="uuid"></list-index> <element type="string" column="name"/></list>
- private Map<Integer, String> farmMap = new HashMap<Integer, String>();
private Map<Integer, String> farmMap = new HashMap<Integer, String>();
- <map name="farmMap" table="TBL_FARM">
- <key column="fk_user_id"/>
- <map-key type="int" column="uuid"/>
- <element type="string" column="name"></element>
- </map>
<map name="farmMap" table="TBL_FARM"><key column="fk_user_id"/><map-key type="int" column="uuid"/><element type="string" column="name"></element></map>
對于集合類型預設是延遲加載的,且隻能單向導航,不能雙向。
5.2.2、實體關聯關系映射:
5.2.2.1、單向關聯關系映射,不示範。
5.2.2.2、雙向關聯關系映射
- 單向
- 定義:不知道另一端什麼情況,擷取一端另一端自動擷取,因為單向,你不知道另一側是什麼。
- 如 class A{ B b;}
- class B{ }
- 隻能從A導航到B,不能從B導航到A
- 關系維護:另一端維護,如B維護
- 雙向
- 定義:知道另一端(兩個單向),從一端擷取另一端,從另一端也能擷取一端
- 如 class A{ B b;}
- class B{ A a;}
- 隻能從A導航到B,也能從B導航到A
- 關系維護:兩端,對關聯的一側所作的改變,會立即影響到另一側
- 關聯的多樣性:
- 從一側看是多對一,從另一側看是一對多
- 另外還有一對一、多對多
- EJB CMP:天生雙向,對關聯的一側所作的改變,會立即影響到另一側,
- 如userGeneral.set(user),則自動調用user.setUserGeneral(userGeneral)
- Hibernate、JPA:天生單向,兩側關系的維護是不同的關聯,必須手工維護
- 如userGeneral.set(user),則需要手工調用user.setUserGeneral(userGeneral)。
單向 定義:不知道另一端什麼情況,擷取一端另一端自動擷取,因為單向,你不知道另一側是什麼。 如 class A{ B b;} class B{ } 隻能從A導航到B,不能從B導航到A 關系維護:另一端維護,如B維護雙向 定義:知道另一端(兩個單向),從一端擷取另一端,從另一端也能擷取一端 如 class A{ B b;} class B{ A a;} 隻能從A導航到B,也能從B導航到A 關系維護:兩端,對關聯的一側所作的改變,會立即影響到另一側關聯的多樣性: 從一側看是多對一,從另一側看是一對多 另外還有一對一、多對多EJB CMP:天生雙向,對關聯的一側所作的改變,會立即影響到另一側, 如userGeneral.set(user),則自動調用user.setUserGeneral(userGeneral)Hibernate、JPA:天生單向,兩側關系的維護是不同的關聯,必須手工維護如userGeneral.set(user),則需要手工調用user.setUserGeneral(userGeneral)。
5.2.2.3、一對一主鍵關系映射(非延遲抓取)
配置1(UserModel.hbm.xml)
- <one-to-one name="userGeneral" cascade="all"/>
<one-to-one name="userGeneral" cascade="all"/>
配置2(UserGeneralModel.hbm.xml)
- <generator class="foreign">
- <param name="property">user</param>
- </generator>
- </id>
- <one-to-one name="user"
- class="cn.javass.h3test.model.UserModel"/>
<id name="uuid"><generator class="foreign"> <param name="property">user</param> </generator></id><one-to-one name="user" class="cn.javass.h3test.model.UserModel"/>
關聯的對象所對應的資料庫表之間,通過一個外鍵引用對主鍵進行限制。
測試:儲存對象,隻需儲存user,自動級聯儲存使用者資訊Model
- UserModel user = new UserModel();
- user.setName("昵稱");
- UserGeneralModel userGeneral = new UserGeneralModel();
- userGeneral.setRealname("真實姓名");
- userGeneral.setUser(user);
- user.setUserGeneral(userGeneral);
- session.save(user);
- //若沒有cascade="all",這句必須
- //session.save(userGeneral);
UserModel user = new UserModel();user.setName("昵稱");UserGeneralModel userGeneral = new UserGeneralModel();userGeneral.setRealname("真實姓名");userGeneral.setUser(user);user.setUserGeneral(userGeneral);session.save(user);//若沒有cascade="all",這句必須//session.save(userGeneral);
1、一對一必須手工維護雙向關系。
2、cascade="all":表示儲存user時自動儲存userGeneral,否則還需要一條save(userGeneral)
3、constrained:添加把userGeneral表的主鍵映射到user主鍵的外鍵限制
5.2.2.4、一對多關系映射(父/子關系映射)
- <set name="farms" cascade="all">
- <one-to-many class="cn.javass.h3test.model.FarmModel"/>
- </set>
<set name="farms" cascade="all"><key column="fk_user_id"/> <one-to-many class="cn.javass.h3test.model.FarmModel"/></set>
配置2(FarmModel.hbm.xml)
- <many-to-one name="user" column="fk_user_id"
- class="cn.javass.h3test.model.UserModel">
<many-to-one name="user" column="fk_user_id" class="cn.javass.h3test.model.UserModel">
- FarmModel farm = new FarmModel();
- farm.setName("farm1");
- farm.setUser(user);
- user.getFarms().add(farm);
- //session.save(farm);//若沒有cascade=all的話需要這條語句
- session.save(user);
UserModel user = new UserModel();user.setName("昵稱");UserGeneralModel userGeneral = new UserGeneralModel();userGeneral.setRealname("真實姓名");userGeneral.setUser(user);user.setUserGeneral(userGeneral);FarmModel farm = new FarmModel();farm.setName("farm1");farm.setUser(user);user.getFarms().add(farm);//session.save(farm);//若沒有cascade=all的話需要這條語句session.save(user);
以上配置有問題:
- insert into TBL_USER (name, age, province, city, street, uuid) values (?, ?, ?, ?, ?, ?)
- insert into TBL_USER_GENERAL (realname, gender, birthday, weight, height, uuid) values (?, ?, ?, ?, ?, ?)
- insert into TBL_FARM (name, fk_user_id, uuid) values (?, ?, ?)
- update TBL_FARM set fk_user_id=? where uuid=?
insert into TBL_USER (name, age, province, city, street, uuid) values (?, ?, ?, ?, ?, ?)insert into TBL_USER_GENERAL (realname, gender, birthday, weight, height, uuid) values (?, ?, ?, ?, ?, ?)insert into TBL_FARM (name, fk_user_id, uuid) values (?, ?, ?)update TBL_FARM set fk_user_id=? where uuid=?
1、持久化user(UserModel);
2、持久化user的一對一關系,即userGeneral(UserGeneralModel);
3、持久化user的一對多關系,即farms(Set<FarmModel>);
3.1、首先發現farm是TO,級聯save;(因為在這可能是PO,PO的話就應該update,而不是save);
3.2、其次發現farm在farms集合中,是以需要更新外鍵(fk_user_id),即執行“update TBL_FARM set fk_user_id=? where uuid=? “。
解決這個問題:
告訴Hibernate應該隻有一端來維護關系(外鍵),另一端不維護;通過指定<set>端的inverse=”true”,表示關系應該由farm端維護。即更新外鍵(fk_user_id)将由farm端維護。
配置修改(UserModel.hbm.xml)
- <set name="farms" cascade="all" inverse="true">
<set name="farms" cascade="all" inverse="true"><key column="fk_user_id"/> <one-to-many class="cn.javass.h3test.model.FarmModel"/></set>
再測試:儲存對象,隻需儲存user,自動級聯儲存使用者資訊Model
- session.save(user);
UserModel user = new UserModel();user.setName("昵稱");UserGeneralModel userGeneral = new UserGeneralModel();userGeneral.setRealname("真實姓名");userGeneral.setUser(user);user.setUserGeneral(userGeneral);FarmModel farm = new FarmModel();farm.setName("farm1");farm.setUser(user);user.getFarms().add(farm);//session.save(farm);//若沒有cascade=all的話需要這條語句session.save(user);
更新外鍵,需要修改FarmModel的外鍵并update:
- insert into TBL_FARM (name, fk_user_id, uuid) values (?, ?, ?)
insert into TBL_USER (name, age, province, city, street, uuid) values (?, ?, ?, ?, ?, ?)insert into TBL_USER_GENERAL (realname, gender, birthday, weight, height, uuid) values (?, ?, ?, ?, ?, ?)insert into TBL_FARM (name, fk_user_id, uuid) values (?, ?, ?)
級聯删除
1、當删除user時自動删除user下的farm
- user = (UserModel) session.get(UserModel.class, 1);
- session.delete(user);
user = (UserModel) session.get(UserModel.class, 1);session.delete(user);
結果:
- Hibernate: delete from TBL_USER_GENERAL where uuid=?
- Hibernate: delete from TBL_FARM where uuid=?
- Hibernate: delete from TBL_USER where uuid=?
Hibernate: delete from TBL_USER_GENERAL where uuid=?Hibernate: delete from TBL_FARM where uuid=?Hibernate: delete from TBL_USER where uuid=?
2、删除user中的farms的一個元素
- UserModel user =
- (UserModel) session.get(UserModel.class, 118);
- FarmModel farm = (FarmModel) user.getFarms().toArray()[user.getFarms().size() - 1];
- user.getFarms().remove(farm);//1.必須先從集合删除
- session.delete(farm);//2.然後才能删除
UserModel user = (UserModel) session.get(UserModel.class, 118);FarmModel farm = (FarmModel) user.getFarms().toArray()[user.getFarms().size() - 1];user.getFarms().remove(farm);//1.必須先從集合删除session.delete(farm);//2.然後才能删除
結果:
- Hibernate: delete from TBL_FARM where uuid=?
Hibernate: delete from TBL_FARM where uuid=?
如果将子對象從集合中移除,實際上我們是想删除它。要實作這種要求,就必須使用
cascade="all-delete-orphan"
。無需再調用session.delete(farm)
5.2.2.5、多對多關系映射:不用
為什麼不使用多對多:當添加新字段時給誰?
那實際項目如何用:拆成兩個一對多。
六、涉及的SQL語句會按照下面的順序發出執行:
1、查詢
1、所有對實體進行插入的語句,其順序按照對象執行Session.save()的時間順序
2、所有對實體進行更新的語句
3、所有進行集合插入的語句 (實體類型)
4、所有對集合元素進行删除、更新或插入的語句 (值類型)
5、所有進行集合删除的語句 (實體類型)
6、所有對實體進行删除的語句,其順序按照對象執行Session.delete()的時間順序
(有一個例外是,如果對象使用native方式來生成ID(持久化辨別)的話,它們一執行save就會被插入。)
七、影響關系映射抓取的cfg配置:
hibernate.max_fetch_depth | 為單向關聯(一對一, 多對一)的外連接配接抓取(outer join fetch)樹設定最大深度. 值為0意味着将關閉預設的外連接配接抓取. 取值 建議在0到3之間取值 |
hibernate.default_batch_fetch_size | 為Hibernate關聯的批量抓取設定預設數量. 取值 建議的取值為4, 8, 和16 |
如果你的資料庫支援ANSI, Oracle或Sybase風格的外連接配接,
外連接配接抓取通常能通過限制往返資料庫次數 (更多的工作交由資料庫自己來完成)來提高效率. 外連接配接抓取允許在單個SELECTSQL語句中, 通過many-to-one, one-to-many, many-to-many和one-to-one關聯擷取連接配接對象的整個對象圖.
将hibernate.max_fetch_depth設為0能在全局 範圍内禁止外連接配接抓取. 設為1或更高值能啟用one-to-one和many-to-one外連接配接關聯的外連接配接抓取, 它們通過 fetch="join"來映射.
八、抓取政策
1、抓取政策定義
抓取政策(fetching strategy) 是指:當應用程式需要在(Hibernate實體對象圖的)關聯關系間進行導航的時候, Hibernate如何擷取關聯對象的政策。抓取政策可以在O/R映射的中繼資料中聲明,也可以在特定的HQL 或
條件查詢(Criteria Query)
中重載聲明。
2、Hibernate3 定義了如下幾種抓取政策:
連接配接抓取(Join fetching) - Hibernate通過 在
SELECT
語句使用
OUTER JOIN
(外連接配接)來獲得對象的關聯執行個體或者關聯集合。 預設非延遲加載
集合抓取需要通過配置fetch="join"來指定。下行資料太多(備援),IO
- //配置 fetch="join"( lazy="true"不起作用了)
- session.get(UserModel.class, 118);//是擷取對象的
- Hibernate: select … from TBL_USER usermodel0_, TBL_FARM farms1_
- where usermodel0_.uuid=farms1_.fk_user_id(+) and usermodel0_.uuid=?
//配置 fetch="join"( lazy="true"不起作用了)session.get(UserModel.class, 118);//是擷取對象的Hibernate: select … from TBL_USER usermodel0_, TBL_FARM farms1_ where usermodel0_.uuid=farms1_.fk_user_id(+) and usermodel0_.uuid=?
查詢抓取(Select fetching) - 另外發送一條
SELECT
語句抓取目前對象的關聯實體或集合。除非你顯式的指定
lazy="false"
禁止延遲抓取(lazy fetching),否則隻有當你真正通路關聯關系的時候,才會執行第二條select語句。
- ////配置 lazy=”true”預設(或者lazy="false" fetch="select")
- Hibernate: select … from TBL_USER usermodel0_ where usermodel0_.uuid=?
- Hibernate: select … from TBL_FARM farms0_ where farms0_.fk_user_id=?
////配置 lazy=”true”預設(或者lazy="false" fetch="select")session.get(UserModel.class, 118);//是擷取對象的Hibernate: select … from TBL_USER usermodel0_ where usermodel0_.uuid=?Hibernate: select … from TBL_FARM farms0_ where farms0_.fk_user_id=?
預設用于lazy="true"情況的集合抓取,如果lazy="false"
,
需要指定fetch="select"來通過查詢抓取。會造成DB的CPU使用率非常高,計算密集
子查詢抓取(Subselect fetching) - 另外發送一條
SELECT
語句抓取在前面查詢到(或者抓取到)的所有實體對象的關聯集合。除非你顯式的指定
lazy="false"
當通過Query等接口查詢多個實體時,如果指定fetch="subselect"則将通過子查詢擷取集合
- ////配置fetch="subselect"
- Query q = session.createQuery("from UserModel");
- System.out.println(q.list());
- Hibernate: select …… from TBL_USER usermodel0_
- Hibernate: select …… from TBL_FARM farms0_ where farms0_.fk_user_id
- in (select usermodel0_.uuid from TBL_USER usermodel0_)
////配置fetch="subselect"Query q = session.createQuery("from UserModel");System.out.println(q.list());Hibernate: select …… from TBL_USER usermodel0_Hibernate: select …… from TBL_FARM farms0_ where farms0_.fk_user_id in (select usermodel0_.uuid from TBL_USER usermodel0_)
批量抓取(Batch fetching) - 對查詢抓取的優化方案, 通過指定一個主鍵或外鍵清單,Hibernate使用單條
SELECT
語句擷取一批對象執行個體或集合。
當通過Query等接口查詢多個實體時,如果指定farm的batch-size="……"則将通過使用單條
SELECT
語句擷取一批對象執行個體或集合。
- List<UserModel> userList = q.list(); System.out.println(userList);
- Hibernate: select … TBL_USER usermodel0_
- Hibernate: select … from TBL_FARM farms0_ where farms0_.fk_user_id in (?, ?)
Query q = session.createQuery("from UserModel");List<UserModel> userList = q.list(); System.out.println(userList);Hibernate: select … TBL_USER usermodel0_Hibernate: select … from TBL_FARM farms0_ where farms0_.fk_user_id in (?, ?)
可指定全局批量抓取政策: hibernate.default_batch_fetch_size,取值:建議的取值為4, 8, 和16。
如果batch-size="4",而某個user有19個農場,Hibernate将隻需要執行五次查詢,分别為4、4、4、4、3。
測試必須資料量足夠多,,如果隻有一條不行
3、使用延遲屬性抓取(Using lazy property fetching)
屬性的延遲載入要求在其代碼建構時加入二進制訓示指令(bytecode instrumentation),如果你的持久類代碼中未含有這些指令, Hibernate将會忽略這些屬性的延遲設定,仍然将其直接載入。
Hibernate3對單獨的屬性支援延遲抓取,這項優化技術也被稱為組抓取(fetch groups)。 請注意,該技術更多的屬于市場特性。在實際應用中,優化行讀取比優化列讀取更重要。但是,僅載入類的部分屬性在某些特定情況下會有用,例如在原有表中擁有幾百列資料、資料模型無法改動的情況下。
4、Hibernate在抓取時會lazy區分下列各種情況:
立即抓取 - 當宿主被加載時,關聯、集合或屬性被立即抓取。
Lazy collection fetching,延遲集合抓取- 直到應用程式對集合進行了一次操作時,集合才被抓取。(對集合而言這是預設行為。)
Extra-lazy" collection fetching,"Extra-lazy"集合抓取-對集合類中的每個元素而言,都是直到需要時才去通路資料庫。除非絕對必要,Hibernate不會試圖去把整個集合都抓取到記憶體裡來(适用于非常大的集合)。
- // lazy="extra"
- Iterator it = q.iterate(); System.out.println(((UserModel)it.next()).getFarms().size());
- //或
- List<UserModel> userList = q.list(); System.out.println(userList.get(0).getFarms().size());
- Hibernate: select usermodel0_.uuid as col_0_0_ from TBL_USER usermodel0_
- Hibernate: select count(uuid) from TBL_FARM where fk_user_id =?
- //或
- Hibernate: select … from TBL_USER usermodel0_
- Hibernate: select count(uuid) from TBL_FARM where fk_user_id =?
// lazy="extra"Query q = session.createQuery("from UserModel");Iterator it = q.iterate(); System.out.println(((UserModel)it.next()).getFarms().size());//或 List<UserModel> userList = q.list(); System.out.println(userList.get(0).getFarms().size());Hibernate: select usermodel0_.uuid as col_0_0_ from TBL_USER usermodel0_Hibernate: select … from TBL_USER usermodel0_ where usermodel0_.uuid=?Hibernate: select count(uuid) from TBL_FARM where fk_user_id =?//或Hibernate: select … from TBL_USER usermodel0_Hibernate: select count(uuid) from TBL_FARM where fk_user_id =?
對于調用size()、contains、isEmpty是一種優化,不讀取所有級聯,而是按條件生産不同的sql。
Proxy fetching,代理抓取 -
對傳回單值的關聯而言,當其某個方法被調用,而非對其關鍵字進行get操作時才抓取。
- //預設 <many-to-one name="user" ……lazy="false"/>
- FarmModel farm = (FarmModel) session.get(FarmModel.class, 121);
- System.out.println(farm.getUser().getUuid());
- Hibernate: select … from TBL_FARM farmmodel0_ where farmmodel0_.uuid=?
- 118
//預設 <many-to-one name="user" ……lazy="false"/>FarmModel farm = (FarmModel) session.get(FarmModel.class, 121);System.out.println(farm.getUser().getUuid());Hibernate: select … from TBL_FARM farmmodel0_ where farmmodel0_.uuid=?Hibernate: select … from TBL_USER usermodel0_ where usermodel0_.uuid=?118
- // <many-to-one name="user" ……lazy="proxy"/>
- 118
// <many-to-one name="user" ……lazy="proxy"/>FarmModel farm = (FarmModel) session.get(FarmModel.class, 121);System.out.println(farm.getUser().getUuid());Hibernate: select … from TBL_FARM farmmodel0_ where farmmodel0_.uuid=?118
注:如果
constrained="false"或基于主鍵的一對一
, 不可能使用代理,Hibernate會采取預先抓取!
"No-proxy" fetching,非代理抓取 - 對傳回單值的關聯而言,當執行個體變量被通路的時候進行抓取。與上面的代理抓取相比,這種方法沒有那麼“延遲”得厲害(就算隻通路辨別符,也會導緻關聯抓取)但是更加透明,因為對應用程式來說,不再看到proxy。這種方法需要在編譯期間進行位元組碼增強操作,是以很少需要用到。
Lazy attribute fetching,屬性延遲加載 -
對屬性或傳回單值的關聯而言,當其執行個體變量被通路的時候進行抓取。需要編譯期位元組碼強化,是以這一方法很少是必要的。
這裡有兩個正交的概念:關聯何時被抓取,以及被如何抓取(會采用什麼樣的SQL語句)。不要混淆它們!我們使用
抓取
來改善性能。我們使用
延遲
來定義一些契約,對某特定類的某個脫管的執行個體,知道有哪些資料是可以使用的。
九、抓取優化
1、集合N+1:
可以使用batch-size來減少擷取次數,即如batch-size=”10”,則是N/10+1。
開啟二級緩存。
對于集合比較小且一定會用到的可采用fetch=”join”,這樣隻需一條語句。
2、笛卡爾積問題:
- <set name="farms" cascade="all,all-delete-orphan" inverse="true" fetch="join">
- <one-to-many class="cn.javass.h3test.model.FarmModel"/>
- </set>
- <set name="hourses" cascade="all,all-delete-orphan" inverse="true" fetch="join">
- <one-to-many class="cn.javass.h3test.model.HourseModel"/>
<set name="farms" cascade="all,all-delete-orphan" inverse="true" fetch="join"><key column="fk_user_id"/> <one-to-many class="cn.javass.h3test.model.FarmModel"/></set><set name="hourses" cascade="all,all-delete-orphan" inverse="true" fetch="join"><key column="fk_user_id"/> <one-to-many class="cn.javass.h3test.model.HourseModel"/></set>
如上配置産生笛卡爾積問題。
select user.*,farm.*,hourse.* from UserModel user, FarmModel farm, HourseModel hourse
where user.uuid=farm.fk_user.uuid(+) and
user.uuid=hourse.fk_user.uuid(+)
解決方案:
1、fetch=”subselect”,子查詢,每個User查詢一套笛卡爾積
2、完全不采用關系映射。
3、大集合采用批處理,按塊擷取集合資料
4、複雜SQL太複雜太慢:找DBA優化,索引等是否有效,是否加載了過多的無用資料,拆分SQL,按需擷取資料。
5、按需擷取1對多中的集合。
6、緩存