hibernate QBC和QBE精講與案列分析(下)
1.使用HQL進行連線查詢
HQL中支援連線查詢分別使用inner join,left outer join,right outer join,其中,左外連線和右外連線可以簡寫為left join和right join。
另外,HQL還支援抓取連線查詢的方式,即使用fetch關鍵字。實際上,是否使用fetch執行的SQL語句基本上是一樣的,它們只是返回不同。
下面分別是內連線、左連線、右連線查詢的例子和它們對應的SQL語句。
下面使用內連線來關聯carorder表和salesman表,即連線的是一個關聯實體。
from CarOrder c inner join c.salesman
它對應的SQL語句如下:
select carorder0_.cid as cid0_0_, salesman1_.sid as sid1_1_, carorder0_.carname as carname0_0_, carorder0_.salesId as salesId0_0_, salesman1_.salesName as salesName1_1_ from carorder1 carorder0_ inner join salesman salesman1_ on carorder0_.salesId=salesman1_.sid
下面使用左連線來關聯carorder表和salesman表,連線的是一個集合中的元素:
from Salesman c left join c.carorders
它對應的SQL語句如下:
select salesman0_.sid as sid1_0_, carorders1_.cid as cid0_1_, salesman0_.salesName as salesName1_0_,
carorders1_.carname as carname0_1_, carorders1_.salesId as salesId0_1_ from salesman salesman0_
left outer join carorder1 carorders1_ on salesman0_.sid=carorders1_.salesId
說明:
雖然Salesman類中的集合carOrders屬性中的字母o用的是大寫,但是和對映檔案要求一樣,這裡也要求使用小寫字母,否則Hibernate找不到這個屬性。
還可以使用where子句為連線查詢設定過濾條件和使用select語句進行屬性投影。
select c.carname from CarOrder c right join c.salesman where c.cid>1
對應的SQL語句如下:
select carorder0_.carname as col_0_0_ from carorder1 carorder0_ right outer join salesman salesman1_ on carorder0_.salesId=salesman1_.sid where carorder0_.cid>1
另外,如果物件間還有別的關聯關係,可以把內連線、左連線、右連線多次結合進行查詢。例如,
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
2.使用Criteria進行連線查詢
使用Criteria介面的createCriteria()函式,可以導航到檢索類關聯實體,進行關聯查詢,這個函式返回的是一個新的Criteria例項。
例如,下面的例子,檢索名字以F開頭的salesman物件關聯的名字以c開頭的carorder物件。
Criteria crit1 = session.createCriteria(Salesman.class);
crit1.add( Restrictions.like("salesname", "F%") );
Criteria crit2 =crit1.createCriteria("carorders");//
crit2.add( Restrictions.like("carname", "c%") );
List results =crit2.list();
在上面的例子中,通過crit1.createCriteria("carorders")關聯到Salesman的carorders集合屬性,返回的是一個指向CarOrder物件的新的Criteria例項,然後在這個例項上設定對CarOrder物件檢索的條件。它的返回是Salesman物件,要得到關聯的carorder物件,還得通過carorders集合來取得。
還可以使用Criteria介面的createAlias()函式為物件的實體屬性設定別名來實現關聯,而且,這樣可以實現的關聯查詢功能往往更強大。createAlias()並不會返回新的例項。
例如,下面的例子與上面使用createCriteria()函式實現的關聯查詢的結果和意思都是一樣的,檢索名字以F開頭的salesman物件關聯的名字以c開頭的carorder物件。
Criteria crit = session.createCriteria(Salesman.class);
crit.add( Restrictions.like("salesname", "F%") );
crit.createAlias("carorders", "carorder");
crit.add( Restrictions.like("carorder.carname", "c%") );
List results2 =crit.list();
在上面的程式碼中,createAlias()並不會建立新的Criteria例項,它還是原來的指向CarOrder物件的Criteria例項,所以當檢索名字以c開頭的carorder物件時,需要使用carorder.carname進行導向。createAlias("carorders", "carorder");的意思是把carorders集合屬性的每個元素設定別名為carorder,所以這時的carorder物件指的是每一個CarOrder物件,這樣就設定了salesman物件與carorder物件的關聯關係。
由於使用了別名,所以,可以進行關聯物件之間任意屬性的匹配,例如下面的例子,檢索carorder中車的名字和salesman中售貨員名字相同的物件。
List results = session.createCriteria(Salesman.class)
.createAlias("carorders", "co")
.add( Restrictions.eqProperty("co.carname", "salesname") )
.list()
如果物件中存在多個關聯的實體物件,可以多次使用createAlias()為它們設定別名,然後也可以進行他們之間屬性或條件的設定。如果關聯的物件還有其他關聯的物件,也可以繼續使用createAlias()來設定別名,只是取關聯實體時用“.”來導航。例如,
List results3 = session.createCriteria(Salesman.class)
.createAlias("carorders", "co")
.createAlias("co.salesman", "cs")
.add( Restrictions.eqProperty("cs.salesname", "salesname") )
.list();
Criteria查詢還支援使用setFetchMode().設定抓取模式,進行關聯抓取,具體在下面進行介紹。
8.2.3 使用fetch和不使用fetch的區別
HQL支援內連線、左外連線和右外連線,HQL進行連線查詢時還支援一種叫做抓取連線查詢的方式,即使用fetch關鍵字。下面以內連線為例子,介紹使用fetch或不使用fetch連線查詢的區別。
假設有兩個POJO類Person和Basiccar,並且Person持久化類中通過basiccar屬性關聯到Basiccar物件。
執行下面的HQL語句,不使用fetch關鍵字:
from Person p inner join p.basiccar
它執行的SQL語句相當於:
select
person0_.id as id7_0_,
basiccar1_.id as id6_1_,
person0_.carId as carId7_0_,
person0_.salesname as salesname7_0_,
basiccar1_.name as name6_1_,
basiccar1_.factory as factory6_1_,
basiccar1_.date as date6_1_
from
test.person person0_
inner join
test.basiccar basiccar1_
on person0_.carId=basiccar1_.id
返回的list中存放的物件是存在關聯關係的Person,Basiccar物件組。即list的每個元素是Object[],其中Object[0]是Person物件,Object[1]是Basiccar物件。
執行下面的HQL語句,使用fetch關鍵字:
from Person p inner join fetch p.basiccar
它執行的SQL語句相當於:
select
person0_.id as id7_0_,
basiccar1_.id as id6_1_,
person0_.carId as carId7_0_,
person0_.salesname as salesname7_0_,
basiccar1_.name as name6_1_,
basiccar1_.factory as factory6_1_,
basiccar1_.date as date6_1_
from
test.person person0_
inner join
test.basiccar basiccar1_
on person0_.carId=basiccar1_.id
可見它們都使用內連線來檢索關聯物件,SQL語句沒有太大差異,但是與不使用fetch相比,這個HQL語句返回的list存放的物件不同。返回的list中存放的物件是存在的主類Person物件,而不是以物件組形式返回關聯的兩個類物件。如果需要得到關聯的Basiccar物件,需要呼叫getBasiccar()方法。
8.3 檢 索 策 略
Hibernate提供多種檢索策略用來設定物件和集合檢索時,物件的載入策略。這個載入策略主要是設定兩個問題,即什麼時候載入,如何載入。
8.3.1 什麼時候載入
關於設定什麼時候載入問題,可以分為類級別的和關聯級別的。
1.類級別
類級別是關於檢索的持久化類什麼時候被載入的策略。Hibernate提供的類級別載入策略包括以下幾種。
● 立即檢索(Immediate fetching):當檢索一個類時,立刻把類的所有屬性都從資料庫檢索出來,然後放到session快取中。
● 代理檢索(Proxy fetching):對類物件進行延遲載入。這時載入的類物件其實是隻有一個主鍵屬性的代理物件。使用這個策略,對於一個類物件,只有在訪問它的非主鍵屬性時,才把真正的物件載入,即才初始化其他物件屬性。
● 非代理檢索("No-proxy fetching):與代理檢索策略一樣,對類進行延遲載入。不同的是,訪問物件的任何屬性,包括主鍵屬性,都會載入真正的類物件。這種方法需要在編譯期間進行位元組碼增強操作,因此很少用到。
● 屬性延遲檢索(Lazy attribute fetching):可以設定具體某個屬性為lazy,即延遲載入。只有在具體地使用get***(),訪問到這個屬性時,這個屬性值才被載入進來。這種方法需要在編譯期間進行位元組碼增強操作,因此很少用到。
上面的4個策略是類級別的,針對什麼時候載入類物件的設定。最後兩種策略需要編譯期間進行位元組碼增強操作,而且是沒有必要的,因為"No-proxy" fetching對使用者來說是透明的,和代理檢索沒有區別;而Lazy attribute fetching,只會使得頻繁訪問資料庫,而且一般多增加一個屬性也不會耗費多少快取,除非是特別大的屬性時可能會用到。另外,如果只是需要物件的某些屬性,更合理的方法應該是使用select語句或者criteria的property()函式進行列的投影。
將在8.3.4節進一步闡述有關立即檢索和代理檢索的運用。
2.關聯級別
關聯級別是關於檢索的主類的關聯實體或者關聯集合什麼時候載入的策略,它可以分成以下幾種。
● 延遲集合檢索(Lazy collection fetching):假設檢索的物件中有集合屬性,例如一對多的關聯,那麼只有在呼叫sets這個集合屬性時,才會把所有符合要求的關聯物件載入。
● 進一步延遲集合檢索("Extra-lazy" collection fetching):它比延遲集合檢索更加延遲。Sets集合屬性中的每一個物件,都只有在具體訪問到這個物件時才把這個關聯物件載入,而不是像延遲集合檢索一樣在訪問集合時即把所有關聯物件載入。
上面的兩個策略是關聯級別的,針對的是什麼時候載入關聯集合物件。
8.3.2 如何檢索
如何檢索策略是針對存在關聯關係的物件的檢索方式。
一般情況下,對資料庫的關聯記錄進行檢索時,有兩種方法進行關聯物件檢索。例如,檢索名字為aaa的salesman物件關聯的carorder物件,可以先檢索合適的salesman物件,再檢索合適的carorder物件。
Select * from salesman where salesname='aaa'
假設檢索出來的salesman物件有兩個,其主鍵分別為1和2,那麼下一步執行:
Select * from carorder where salesId=1;
Select * from carorder where salesId=2;
則需要通過3條SQL語句來實現。
另一種選擇是,也可以使用連線查詢,這樣就只要一條SQL語句即可。例如,上面的操作可以使用左連線。程式碼如下:
select * from salesman ss left outer join carorder co on ss.sid=co.salesId where ss. salesname='aaa'
Hibernate對於如何載入關聯物件,即如何抓取關聯物件提供的4種策略,分別是:
● 連線檢索(join fetching),即在檢索關聯物件時,採用outer join的select來進行關聯查詢,如上面提到的第二種方法。不管進行關聯物件的立即載入時可以選擇這種方式,這樣只要一條SQL語句就可以把主物件和關聯物件同時檢索進快取。
● 查詢檢索(Select fetching),即在檢索關聯物件時,先檢索主物件。根據主物件連線關聯物件的外來鍵來作為檢索關聯物件的查詢條件,然後檢索關聯物件,如上面提到的第一種方法。所以不管立即載入還是延遲載入都可以使用這種方法。
● 子查詢檢索(Subselect fetching),另外發送一條select語句抓取在前面查詢到(或者抓取到)的所有實體物件的關聯集合。除非顯式地指定lazy="false" 禁止延遲抓取(lazy fetching),否則只有在真正訪問關聯關係的時候,才會執行第二條select語句。與上面第一種select抓取方式不同的是,select fetching只會檢索載入實體的關聯集合;而這裡是檢索查詢到的所有實體的關聯集合。具體在8.3.5節的集合的fetch屬性中介紹。
● 批量檢索(Batch fetching),對查詢抓取的優化方案, 通過指定一個主鍵或外來鍵列表,Hibernate使用單條SELECT語句獲取一批物件例項或集合。將在8.3.6節做介紹。
下面將對各種主要的檢索策略進行介紹。
8.3.3 類級別的延遲載入
對於類級別的延遲載入策略,也就是代理檢索策略,即在載入持久化類物件時先不初始化類的屬性,只是生成一個代理類,在必要時再載入物件屬性初始化類。延遲載入只會在呼叫load函式,載入物件時有用。如果使用get函式,或者使用Query.list()執行查詢,都會立即載入相應的類物件。
設定類的延遲載入[即只有在呼叫物件的(除主鍵外)屬性時,才執行select語句,從資料庫中載入物件初始化物件的屬性]方法是,在對映檔案的類中增加lazy屬性,它有兩個值,分別是true和false,如果設定lazy="true"的屬性,或者不設定這個lazy屬性,因為hibernate預設的類就是延遲載入。如下所示:
…省略了其他屬性的對映內容
這個設定,當執行下面的程式碼:
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
其實hibernate並不會執行select語句從資料庫中讀取資料。這時返回的salesman例項,其實是一個id為1,其他屬性都為空的代理類。這樣,可以節約記憶體,在必要的時候才載入物件。
如果,訪問這個salesman物件的屬性,那麼就會觸發hibernate去讀取資料庫。例如,
ss.getSalesname();
那麼hibernate會執行下面的sql語句,把其他屬性也載入:
select salesman0_.sid as sid1_0_, salesman0_.salesName as salesName1_0_ from salesman salesman0_ where salesman0_.sid=1
需要說明的是,如果是取主鍵,不會觸發讀取物件的操作。例如,
ss.getSid();
它仍是從代理類返回主鍵值。
所以,如果load檢索的物件不存在,那麼,在load()函式執行時是不會發現的,只有在get***()訪問物件的某個非主鍵屬性時才丟擲物件不存在的異常。
另外,物件只能在session範圍內被載入,如果session被關閉了,這時再返回物件,就會丟擲異常。例如下面的程式碼:
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
ss.getSid();
session.close();
ss.getSalesname();
由於在session關閉前沒有初始化物件,所以這時的salesman還是代理類,則呼叫ss.getSalesname()時無法在session範圍內載入,從而丟擲異常:could not initialize proxy - no Session。
如果需要顯示地把物件初始化,除了呼叫get方法訪問屬性外,還可以呼叫hibernate提供的靜態方法initialize()來初始化物件。例如,
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
Hibernate.initialize(ss);
session.close();
這樣也會觸發select語句載入物件。
如果不設定延遲載入策略,即對映檔案中設定lazy="false",那麼在呼叫load函式時,就會立即執行select語句,初始化物件屬性來載入物件。
8.3.4 關聯實體的載入策略
下面來看看,對於存在關聯關係的實體,它又有哪些可選策略來載入關聯類物件。
下面介紹有關的持久化類和對映檔案。
CarOrder物件通過屬性salesman關聯到Salesman實體。其中CarOrder物件程式碼如下所示:
public class CarOrder implements java.io.Serializable {
private long cid;
private String carName;
private Salesman salesman;
//省略了建構函式和其他getter/setter方法
其對應的對映檔案的主要部分如下所示:
column="salesId"
class="basicCar.bean.Salesman"
cascade="all"
not-null="false"
lazy="proxy"
fetch="select">
這裡,對於在載入與carorder物件關聯的salesman物件時,採用怎樣的載入策略,是由中的lazy和fetch屬性,以及salesman對映檔案類級別的lazy屬性決定的。
1.中的lazy屬性
中的Lazy屬性決定關聯實體什麼時候載入,它有3個值可以設定,分別是proxy、no-proxy、false。
● 如果設定proxy,那麼表示對關聯實體採取延遲載入策略,也就是說,在Carorder物件被載入的同時,只是載入salesman物件的代理類,所以這時的salesman物件除了sid,其他屬性都是空的。只有在呼叫salesman物件的非主鍵屬性的getter方法時,這個物件才被載入。
● 如果設定no-proxy指定此屬性,應該在例項變數第一次被訪問時延遲抓取(fetche lazily)(需要執行時位元組碼的增強)。這個設定不常用。本章不做介紹。
● 如果設定false,那麼表示對關聯實體採取立即檢索策略,也就是說,在Carorder物件被載入的同時,把salesman物件也載入。
但是,是否延遲載入,它同時也會參考關聯物件自身是否延遲載入的設定,也就是說,salesman.hbm.xml的class元素的lazy屬性為true還是false也會影響延遲載入。中的Lazy屬性設定,結合關聯類的lazy屬性設定,載入情況如表8-3所示。
表8-3 類級別的載入策略
中的Lazy屬性
關聯類的lazy屬性
關聯類什麼時候被載入
false
true
立即載入
No-proxy/proxy
true
延遲載入
No-proxy/proxy
false
立即載入
false
false
立即載入
預設(proxy)
預設(true)
延遲載入
說明:
這裡的延遲或立即載入,是指當主類被載入時,何時載入關聯類物件。立即載入,即主類被載入時就把關聯類物件載入;延遲載入,即主類被載入時不把關聯類載入,直到通過這個實體屬性,訪問這個關聯類的某個非主鍵屬性時才把關聯類載入。至於主類何時載入,主要根據主類的類級別的lazy屬性設定為true還是false。
在本例中,如果在中設定lazy="false",也就是對關聯的salesman物件進行立即檢索。那麼在carorder物件被載入時,salesman物件也被載入。例如,如果在carorder.hbm.xml中carorder的類級別檢索策略設定為立即檢索,也就是
那麼執行下面的程式碼:
CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
會把carorder物件和關聯的salesman物件都載入記憶體。
如果在中設定lazy="false",也就是對關聯的salesman物件進行立即檢索,然後在carorder.hbm.xml中,carorder的類級別檢索策略設定為延遲檢索,也就是:
那麼會在檢索carorder物件的任何非主鍵屬性(包括carName和salesman屬性)時把關聯的salesman物件都載入記憶體。例如,執行下面程式碼:
CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
co.getCarname();
這樣把carorder物件載入的同時,把關聯的salesman物件也載入記憶體。
上面演示的例子都是對關聯物件執行立即檢索。
如果在carorder的對映檔案中把元素設定如下:
…
column="salesId"
class="basicCar.bean.Salesman"
cascade="all"
not-null="false"
lazy="proxy"
fetch="select">
…
然後salesman中的類級別延遲屬性設定為true,即如下:
…
那麼,會對關聯類salesman進行延遲載入,即以下的程式碼只會載入carorder物件,但是不會載入與之關聯的salesman物件:
CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
co.getCid();
co.getCarname();
Salesman salesman= co.getSalesman();
但是,如果訪問salesman物件的某個非主鍵屬性時,就會使用select語句把salesman物件載入記憶體。例如,下面的語句:
ss.getSalesname();
ss.getCarorders();
都可以,即除了getSid()外,其他不管集合還是普通值屬性都會初始化salesman物件。
2.中的fetch屬性
中的fetch屬性有兩個值可以選擇,即join和select。join代表使用連線查詢檢索物件。select則代表另外發送select 語句抓取在前面查詢到(或者抓取到)的所有實體物件的關聯物件。預設是使用select值。
使用join會在檢索主類物件時,使用連線查詢,把關聯物件也載入,這樣就需要一條sql語句就可以檢索主物件和其關聯物件了。例如,下面carorder對應的對映檔案段落,設定關聯類是使用代理進行延遲載入,並且載入方式是select:
…
column="salesId"
class="basicCar.bean.Salesman"
cascade="all"
not-null="false"
lazy="false"
fetch="join">
…
那麼執行下面的程式碼時:
CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
co.getCarname();
會使用外連線把carorder物件和關聯的salesman物件都載入,相當於執行了下面的SQL語句;
select c.cid, c.carname, c.salesId, s.sid, s.salesName from carorder1 c left outer join salesman s on c.salesId=s.sid where c.cid=1
由於使用連線查詢把關聯物件同時載入,所以對於延遲檢索設定join抓取模式是沒有意義的。例如上面設定lazy="proxy",還是使用連線查詢把關聯物件也同時載入。
使用select會先檢索主類物件。至於何時檢索與符合條件的類關聯的關聯類,就要看關聯類設定的是延遲載入還是立即載入策略了。
例如,下面的carorder對應的對映檔案段落如下,設定關聯類是使用代理進行延遲載入,並且載入方式是select:
…
column="salesId"
class="basicCar.bean.Salesman"
cascade="all"
not-null="false"
lazy="proxy"
fetch="select">
…
那麼執行下面的程式碼:
CarOrder co=(CarOrder) session.load(CarOrder.class, new Long(1));
co.getCarname();
相當於執行了select * from carorder where cid=1把carorder物件載入。假設這個carorder物件中的salesId=13。
然後在訪問關聯的salesman物件的非主鍵屬性時才使用select語句載入
Salesman ss= co.getSalesman();
ss.getSalesname();
相當於執行了select * from salesman s where s.sid=13把關聯物件載入。如果對關聯物件設定的是立即載入策略,那麼在載入carorder物件時也執行這條SQL語句載入salesman。
關聯的實體物件,其fetch和lazy值的選擇和設定意義與上面描述的一樣。
8.3.5 關聯集合的載入策略
Salesman物件從通過集合屬性carOrders關聯到CarOrder實體集合。其中Salesman物件程式碼如下所示:
public class Salesman implements java.io.Serializable{
private long sid;
private String salesName;
private Set carOrders = new HashSet();
//省略了建構函式和其他getter/setter方法
其對應的對映檔案主要部分如下所示:
與對映java集合相關的元素包括、、和。在這些集合元素中與載入策略有關的屬性有set和lazy。這裡,對於在載入與salesman物件關聯的集合carorder物件時,採用什麼載入策略,是由中的lazy和fetch屬性決定的。
1.集合的lazy屬性
集合元素的lazy屬性決定集合屬性什麼時候載入,它有3個值可以選擇,即true、extra和false。
● 如果設定true,表示延遲集合檢索(Lazy collection fetching)。即只有在呼叫sets這個集合屬性時,才會把所有符合要求的關聯物件載入。在這個例子中,也就是說,在Saleman物件被載入的同時,只是載入carorders這個集合屬性的集合代理類例項,真正的集合中的每一個關聯的carorder例項並沒有被載入。只有在應用程式第一次訪問這個例項時,才初始化這個集合。
● 如果設定extra,表示進一步延遲集合檢索"Extra-lazy" collection fetching。即Sets集合屬性中的每一個物件,都只有在具體訪問到這個物件時才把這個關聯物件載入。例如本例子中,在訪問集合時,不會把所有carorder例項都載入,只有在具體訪問某個關聯carorder時才把它載入。
● 如果設定false,表示對關聯實體採取立即檢索策略,也就是說,在salesman物件被載入的同時,把所有關聯的carorder物件也載入,初始化集合carorders。
在本例中,如果在元素中設定lazy="false",也就是對關聯的carorders集合進行立即檢索。那麼在salesman物件被載入時,carorders集合也被載入。例如,如果在salesman.hbm.xml中,salesman的類級別檢索策略設定為延遲檢索,也就是:
那麼執行下面的程式碼,即load這個Salesman物件訪問它的非主鍵屬性時,會把salesman物件載入:
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
ss.getSalesname();
這時,如果salesman.hbm.xml的元素中設定lazy="false",並且fetch設定為fetch="select"(使用select方式來檢索相關類,語義和的fetch="select"相同),那麼上面的程式碼也會載入關聯的carorder物件初始化carorders集合,相當於執行下面的sql語句:
select * from salesman s where s.sid=1
select * from carorder1 c where c.salesId=1
這樣把salesman物件載入的同時,把關聯的carorder物件也都載入記憶體初始化carorders集合。
上面演示的例子都是對關聯物件執行立即檢索。
如果在salesman的對映檔案中把元素設定如下,即lazy="true",對集合中進行延遲載入:
…
…
那麼,會對關聯類salesman進行延遲載入,即以下的程式碼只會載入carorder物件,但是不會載入與之關聯的carorder物件初始化集合屬性:
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
ss.getSalesname();
Set carOrders=ss.getCarorders();
這時只相當於執行了SQL語句:
select * from salesman s where s.sid=1
如果使用這個carOrders集合,即呼叫carOrders集合的任何函式[getClass()除外],都會觸發select語句讀取資料庫,把所有關聯的carorder物件載入記憶體。例如下面的語句都可以:
carOrders.isEmpty();
carOrders.iterator();
這時如果set元素的fetch設定為fetch="select",(使用select方式來檢索相關類,語義和的fetch="select"相同),那麼相當於執行SQL語句:
select * from carorder1 c where c.salesId=1
即載入所有的carorder物件初始化集合。
如果在salesman的對映檔案中把元素設定如下,即lazy="extra",對集合中每一個元素都進行延遲載入:
…
…
那麼,會對關聯類salesman進行延遲載入,即以下的程式碼只會載入carorder物件,但是不會載入與之關聯的carorder物件初始化集合屬性:
Salesman ss=(Salesman) session.load(Salesman.class, new Long(1));
ss.getSalesname();
Set carOrders=ss.getCarorders();
這時只相當於執行了SQL語句:
select * from salesman s where s.sid=1
如果使用這個carOrders集合,即呼叫carOrders集合的函式並不一定會觸發select語句讀取資料庫,把所有關聯的carorder物件載入記憶體,它只會在所有函式需要載入所有資料時才select所有物件。例如,下面的語句不會使select所有關聯物件:
carOrders.isEmpty();
它只是判斷集合是否為空,所以只會觸發執行下面的sql語句:
select count(cid) from carorder1 where salesId =1
但是如果執行有些需要訪問所有物件的函式就會觸使初始化所有物件。例如,
carOrders.iterator();
這時如果set元素的fetch設定為fetch="select",那麼相當於執行SQL語句:
select * from carorder1 c where c.salesId=1
即載入所有的carorder物件初始化集合。
2.集合的fetch屬性
中的fetch屬性有3個值可以選擇,即join、subselect和select。它代表對關聯集合採取什麼SQL語句進行資料庫讀取,預設值為select。join代表使用連線查詢檢索物件。select則代表另外發送select 語句抓取在前面查詢到(或者抓取到)的所有實體物件的關聯物件。預設使用select值。
● 如果設定為join,那麼不管lazy設定為什麼屬性值,都會使用外連線立即檢索主類和與主類關聯的所有物件,初始化集合物件。join值和select值與的fetch屬性上介紹的一樣,這裡不再贅述。
● Subselect屬性值代表把本次查詢得到的所有主類的關聯集合都初始化。
例如,假設資料庫中存在salesman物件和carorder物件,資料及其關係如下:
圖8-4 資料庫類物件及其關係
然後在資料庫中執行如下語句:
Salesman ss=(Salesman)session.createQuery("from Salesman").list().get(0);
ss.getSalesname();
Set carOrders=ss.getCarorders();
carOrders.isEmpty();
這裡的HQL語句載入了所有的salesman物件,包括salesman1和salesman2,但是get(0)只會訪問salesman1物件。
假設設定fetch="select",那麼只會載入與salesman1關聯的carorder1和carorder2物件,執行的SQL語句:
select * from salesman
select * from carorder c where c.salesId=1
只檢索載入的salesman1關聯的carorder物件。
假設設定fetch="subselect",那麼會把檢索到的所有salesman物件關聯的集合carorders都初始化。則載入與salesman1關聯的carorder1和carorder2物件,載入與salesman2關聯的carorder3,執行的sql語句:
select * from salesman
select * from carorder c where c.salesId in (select s.sid from salesman s)
只檢索第一次select得到的所有實體的關聯集合。
8.3.6 batch載入策略
關聯實體的載入和關聯集合的載入都可以設定批量檢索屬性,即、和集合元素、等都有batch-size="N"屬性,值為一個整數,說明每次批處理的個數。下面以元素的batch-size為例,說明批量檢索的意思。
先設定fetch="select",不設定batch-size值,那麼,執行下面的方法:
List results=session.createQuery("from Salesman").list();
Iterator sales=results.iterator();
Salesman sale1=(Salesman)sales.next();
Salesman sale2=(Salesman)sales.next();
Iterator order1=sale1.getCarorders().iterator();
Iterator order2=sale2.getCarorders().iterator();
呼叫列表的iterator的next()方法可以不斷地遍歷列表,讀出物件。上面的程式碼從資料庫中載入了salesman1和salesman2物件後,並呼叫它們的getCarorders().iterator(),通過兩個select語句把它們關聯的carorder物件分別載入。
SQL語句如下:
select * from salesman
select * from carorder1 c where c.salesId=1
select * from carorder1 c where c.salesId=2
這樣使用的SQL語句比較多。
如果在對映檔案中設定batch-size屬性。例如下面的對映檔案片斷:
那麼當訪問sale1.getCarorder().iterator()方法時,由於session的快取中,salesman1和salesman2關聯的2個carorders集合代理類沒有被初始化,由於元素的batch-size="3",即最多批量初始化3個carorders集合代理類,所以,salesman1和salesman2關聯的carorders集合都會被同時初始化。上面的程式碼執行的SQL語句如下:
select * from salesman
select * from carorder1 c where c.salesId=1 or c.salesId=2
當訪問sale2.getCarorder().iterator()方法時,就不需要再初始化它的carorders集合代理類了。
可見,設定批量處理,可以把快取中的代理類進行批量初始化。這樣可以減少資料庫的訪問次數。
8.4 小 結
本章首先講解了Hibernate的另一種常用的面向物件的檢索方式,即QBC檢索方式。然後介紹了在資料查詢中經常用到的連線查詢在Hibernate中的HQL和QBC檢索方式的實現。最後,詳細地介紹了Hibernate3提供的各種持久化物件、關聯實體和關聯集合的檢索方式,即設定什麼時候載入物件,如何載入,以優化檢索效能。