看這個鏈接作了解:http://zhaohe162.blog.163.com/blog/static/3821679720110251181721/
一、HQL查詢的from子句
from是最簡單的語句,也是最基本的HQL語句。from關(guān)鍵字后緊跟持久化類的類名。
例如:
from Person 表明從Person持久化類中選出全部的實例
推薦為Person持久化類的每個實例起別名,例如:
from Person as p
p作為Person的實例的別名,因此也應(yīng)該遵守Java的命名規(guī)則:第一個單詞的首字母小寫,后面每個單詞的首字母大寫。
命名別名時,as關(guān)鍵字是可選的,但為了增加可讀性,建議保留。
from 后還可同時出現(xiàn)多個持久化類,此時將產(chǎn)生一個笛卡爾積或跨表的連接,但實際上這種用法很少使用,因為通常我們可能需要使用跨表的連接,此時可以考慮使用隱式連接或顯示連接,而不是直接在from后緊跟多個表名。
二、關(guān)聯(lián)和連接
Hibernate使用關(guān)聯(lián)映射來處理底層數(shù)據(jù)表之間的連接,一旦我們提供了正確的關(guān)聯(lián)映射后,當(dāng)程序通過Hibernate進(jìn)行持久化訪問時,將可利用Hibernate的關(guān)聯(lián)來進(jìn)行連接。
HQL支持兩種關(guān)聯(lián)連接(join)的形式:隱式(implicit)與顯示(explicit)。
隱式連接形式不適用join關(guān)鍵字,使用英文點(diǎn)號(.)來隱式連接來關(guān)聯(lián)實體,而Hibernate底層將自動進(jìn)行關(guān)聯(lián)查詢。如下HQL語句:
from Person p where p.myEvent.title > :title
上面的p.myEvent屬性的實質(zhì)是一個持久化實體,因此Hibernate底層隱式地自動進(jìn)行連接查詢。
顯示連接則需要使用xxx join關(guān)鍵字,例如如下語句:
//使用顯示連接
from Person p
inner join p.myEvent event
where event.happenDate < :endDate
使用顯示連接時,可以為相關(guān)聯(lián)的實體,甚至是關(guān)聯(lián)集合中的全部元素指定一個別名。
Hibernate支持的HQL連接類型,可使用如下幾種連接方式:
inner join(內(nèi)連接),可簡寫成join。
left outer join(左外連接),可簡寫成left join。
right outer join(右外連接),可簡寫成right join。
full join(全連接),并不常用。
使用顯示連接時,還可通過HQL的with關(guān)鍵字來提供額外的連接條件,例如如下HQL語句:
from Person p
inner join p.myEvent event
with p.id > event.id
where event.happenDate < :endDate
HQL語句中的with關(guān)鍵字的作用基本等同于SQL99中on關(guān)鍵字的作用:都是用于指定連接條件。通過在HQL語句中使用with關(guān)鍵字,可以讓HQL語句執(zhí)行非等值連接查詢。
由于表連接的方式都是基于底層SQL來實現(xiàn)的,如果底層不支持這些外連接,那么執(zhí)行對應(yīng)的HQL時就會相應(yīng)地引發(fā)異常。
三、隱式連接和顯示連接有如下兩點(diǎn)區(qū)別:
(1)隱式連接底層轉(zhuǎn)換成SQL92的內(nèi)連接,顯示連接層將轉(zhuǎn)換成SQL99的多表連接。
(2)隱式連接和顯示連接查詢后返回的結(jié)果不同。
當(dāng)HQL語句中省略select關(guān)鍵字時,使用隱式連接查詢返回的結(jié)果是多個被查詢實體組成的集合。
當(dāng)HQL語句中省略select關(guān)鍵字時,使用顯示連接查詢返回的結(jié)果也是集合,但集合元素是被查詢持久化對象、所有被關(guān)聯(lián)的持久化對象所組成的數(shù)組。
注意:
對于Hibernate3.2.3以后的版本,如果關(guān)聯(lián)實體是單個實體或單個的組件屬性,HQL依然可以似乎用英文點(diǎn)號(.)來隱式連接關(guān)聯(lián)實體或組件;但如果關(guān)聯(lián)實體是集合(包括1-N關(guān)聯(lián)、N-N關(guān)聯(lián)和集合元素時組件等),則必須使用xxx join來顯示連接關(guān)聯(lián)實體或組件。
對于集合屬性的,Hibernate默認(rèn)采用延遲加載策略,解決辦法:
(2.1)可以在Hibernate映射文件中指定lazy="false"來關(guān)閉延遲加載。
(2.2)使用join fetch,通常無須指定別名,因為相關(guān)聯(lián)的對象不應(yīng)當(dāng)在where子句(或其他任何子句)中使用。而且被關(guān)聯(lián)的對象也不會再被查詢的結(jié)果中直接返回,而是應(yīng)該通過其父對象來訪問。
使用fetch關(guān)鍵字時,有如下幾個注意點(diǎn):
fetch不應(yīng)該與setMaxResults()或setFirstResult()共用。因為這些操作是基于結(jié)果集的,而在預(yù)先抓取集合類時可能包含重復(fù)的數(shù)據(jù),即無法預(yù)先知道精確的行數(shù)。
fetch不能與獨(dú)立的with條件一起使用。
如果在一次查詢中fetch多個集合,可以查詢返回笛卡爾積,因此請多加注意。
對bag映射而言,同時join fetch多個集合時可能出現(xiàn)非預(yù)期結(jié)果,因此需要謹(jǐn)慎使用。
full join fetch 與right join fetch是沒有任何意義的。
程序里希望預(yù)加載那些原本應(yīng)延遲加載的屬性,則可以通過fetch all properties來強(qiáng)制Hibernate立即抓取這些屬性。例如:
from Document fetch all properties order by name
四、HQL查詢的select子句
(1)select子句用于選擇指定的屬性或直接選擇某個實體,當(dāng)然select選擇的屬性必須是from后持久化類包含的屬性。
例如:
select p.name from Person as p
(2)select可以選擇任意屬性,即不僅可以選擇持久化類的直接屬性,還可以選擇組件屬性包含的屬性。例如:
select p.name.firstName from Person as p
(3)在特殊情況下,如果select后只有一項(包括持久化實例或?qū)傩裕瑒t查詢得到的集合元素就是該持久化實例或?qū)傩浴?/p>
(4)如果select后有多個項,則每個集合就是選擇出的多項組成的數(shù)組。
例如:
select p.name,p from Person as p
執(zhí)行該HQL語句得到的集合元素時類似于[String,Person]結(jié)構(gòu)的數(shù)組,其中第一個元素時Person實例的name屬性,第二個元素時Person實例。
注意:即使select后的列表項選出某個持久化類的全部屬性,這些屬性依然是屬性,Hibernate不會將這些屬性封裝成對象。只有在select后的列表里給出持久化類的別名(其實就是實例名),Hibernate才會將該項封裝成一個持久化實體。
(5)select支持將選擇出的屬性存入一個List對象中。
例如:
select new list(p.name, p.address) from Person as p
(6)select支持將選擇出的屬性封裝成對象,前提是該對象擁有支持這些屬性的構(gòu)造器。
例如:
select new ClassTest(p,name, p.address) from Person as p;
而ClassTest必須有如下的構(gòu)造器:ClassTest(String s1, String s2)
(7)select還支持給選中的表達(dá)式命名別名。例如:
select p.name as personName from Person as p
這種用法與new map 結(jié)合使用更普遍。例如:
select new map(p.name as personName) from Person as p
執(zhí)行上面的HQL語句返回的結(jié)果是集合,其中集合元素時Map對象,以personName作為Map的key,實際選出的值作為Map的value。
五、HQL查詢的聚集函數(shù)
HQL支持的聚集函數(shù)與SQL的完全相同:avg,count,max,min,sum。
例如,如下的HQL語句:
select count(*) from Person
select max(p.page) from Person as p
select子句還支持字符串連接符、算術(shù)連接符,以及SQL函數(shù)。例如:
select p.name||""||p.address from Person as p
select子句也支持使用distinct和all關(guān)鍵字,此時的效果與SQL的效果完全相同。
六、多態(tài)查詢
HQL語句被設(shè)計成能夠理解多態(tài)查詢,from后跟持久化類名,不僅會查詢出該持久化類的全部實例,還會查詢出該類的子類的全部實例。
如下面的查詢語句:from Person as p
如果Named接口有多個持久化實現(xiàn)類,下面的語句將返回這些持久化類的全部實例。
如下面的查詢語句:from Named as n
注意:上面最后一條查詢,需要多個SQL SELECT 語句,因此無法使用order by子句對結(jié)果集排序,從而不允許對這些查詢結(jié)果使用Query.scroll()方法。
七、HQL查詢的where子句
(1)where子句用于篩選選中的結(jié)果,縮小選擇的范圍。如果沒有為持久化實例命名別名,則可以直接使用屬性名來引用屬性。
如下面兩條HQL語句:
from Person where name like "tom%"
form Person as p where p.name like "tom%"
(2)復(fù)合屬性表達(dá)式加強(qiáng)了where子句的功能,例如,如下的HQL語句:
from Cat cat where cat.mate.name like "kit%"
上面語句被翻譯成以下含有內(nèi)連接的SQL查詢:
select * from cat_table as table1 cat_table as table2
where table1.mate = table2.mate
and table1.name like '''kit%'"
實際上這種用法使用了隱式連接查詢,從Hibernate3.2.3之后,只有當(dāng)cat,mate屬性引用的是普通組件屬性或者單獨(dú)的關(guān)聯(lián)實體時才可接著在后面使用點(diǎn)好(.)來引用mate屬性,如cat,mate.name;如果cat,mate是集合屬性,Hibernate3.2.3以后的版本不支持這種用法。
"="號不僅可以被用來比較屬性的值,也可以用來比較實例。
select cat,mate from Cat cat, Cat mate where cat.mate = mate
(3)在進(jìn)行多態(tài)持久化的情況下,class關(guān)鍵字用來存取一個實例的鑒別值。嵌入where子句的Java類名,將被作為該類的鑒別值。
//執(zhí)行多態(tài)查詢時,默認(rèn)會選出Cat及其所有子類的實例
//在如下HQL語句中,將只選出DomesticCat類的實例
from Cat cat where cat.class = DomesticCat
(4)當(dāng)where子句中的運(yùn)算符只支持基本類型或者字符串時,where子句中的屬性表達(dá)式必須以基本類型或者字符串結(jié)尾,不要使用組件類型屬性結(jié)尾,例如Account和Person屬性,而Person有Name屬性,Name屬性有firstName屬性。
如下所示:
//firstName是字符串
from Account as a where a.person.name.firstName like "dd%"
//下面是錯誤實例
from Account as a where a.person.name like "dd%"
八、表達(dá)式
1、HQL的功能非常豐富,where子句后支持的運(yùn)算符,不僅包括SQL的運(yùn)算符,也包括EJB-QL的運(yùn)算符等。
where子句中允許使用大部分SQL支持的表達(dá)式,包括如下種類:
(1)字符串連接:如value1||value2,或使用字符串連接函數(shù)concat(value1, value2)
(2)簡單的case,case...when...then...else...end和case,case when...then...else...end等。
(3)時間操作函數(shù):current_date()、current_time、year()等。
(4)EJB-QL3.0的函數(shù):substring()、trim()、abs()、sqrt()等。
(5)可在where子句中使用SQL常量。
(6)在HQL語句中使用Java中的public static final類型的常量,例如Color.RED
(7)HQL語句支持使用英文問號(?)作為參數(shù)占位符,這與JDBC的參數(shù)占位符一致;也使用命名參數(shù)占位符號,方法在參數(shù)名前加英文冒號(:),例如:start_date等。
(8)如果底層數(shù)據(jù)庫支持單行函數(shù),則HQL語句也完全可以支持。
(9)支持?jǐn)?shù)據(jù)庫的類型轉(zhuǎn)換函數(shù),如cast(... as ...),第二個參數(shù)是Hibernate的類型名,或者extract(... from ,,,),前提是底層數(shù)據(jù)庫支持ANSI cast()和extract()。
2、如果在Hibernate配置文件中進(jìn)行如下聲明:
<property name="hibernate.query.substitutions">true 1,false 0</property>
上面的聲明表明:HQL轉(zhuǎn)換SQL語句時,將使用字符1和0來取代關(guān)鍵字true和false,然后將可以在表達(dá)式中使用布爾表達(dá)式。
3、有用的elements()和indices函數(shù),用于返回指定集合的所有元素和所有索引。
4、在where子句中,有序集合(數(shù)組、List集合、Map對象)的元素可以通過【】運(yùn)算符來訪問。如下:
from Order order where order.items[0].id=1234
在【】中的表達(dá)式甚至可以是一個算式表達(dá)式。
5、結(jié)構(gòu)變量:size、elements、indices等,只能在where子句中使用。
九、order by子句
查詢返回的集合根據(jù)類或組件屬性的任何屬性進(jìn)行排序。例如:
from Person as p order by p.name,p.age
還可以使用as或desc關(guān)鍵字指定升序或降序的排序規(guī)則。例如:
from Person as p order by p.name asc,p.age desc
如果沒有指定排序規(guī)則,默認(rèn)采用升序規(guī)則。
十、group by子句
返回聚集值的查詢可以對持久化類或組件屬性的進(jìn)行分組,分組使用group by子句。看下面的HQL查詢語句:
select cat,color,sum(cat.weight),count(cat) from Cat cat group by cat.color
其規(guī)則類似于SQL規(guī)則。
having子句用于對分組進(jìn)行過濾,如下所示:
select cat,color,sum(cat.weight),count(cat) from Cat cat group by cat.color having cat.color in (eg.Color.TABBY,eg.Color.BLACK)
注意:group by子句與order by 子句中都不能包含算術(shù)表達(dá)式
十一、子查詢
(1)如果底層數(shù)據(jù)庫支持子查詢,則可以在HQL語句中使用子查詢。如下:
from Cat as fatcat where fatcat.weight > (select avg(cat.weight) from DomesticCat cat)
(2)如果子查詢是多行結(jié)果集,則應(yīng)該使用多行運(yùn)算符。如下:
from Cat as cat where not(cat.name, cat.color) in (select cat.name, cat.color from DomesticCat cat)
(3)SQL語法中子查詢還可以出現(xiàn)在select子句之后,HQL也支持這種用法,看如下HQL語句:
select cat.id,(select max(kit.weight) from cat.kitten kit)
from Cat as cat
注意:HQL子查詢只可以在select子句或者where子句中出現(xiàn)。
十二、命名查詢
HQL支持將查詢所用的HQL語句放入配置文件中,而不是代碼中。
在Hibernate映射文件的<hibernate-mapping/>元素中使用<query/>子元素來定義命名查詢,使用<query/>元素只需要指定一個name屬性,指定該命名查詢的名字。該元素的內(nèi)容就是命名查詢的HQL語句。如下配置文件片段:
<query name="myNameQuery">
from Person as p where p.age > ?
</query>
配置好后,通過Session提供的一個getNameQuery(String name)方法,該方法用于創(chuàng)建一個Query對象,剩下的操作與普通HQL完全一樣。如下所示:
List p1 = sess.getNamedQuery("myNameQuery").setInteger(0,20).list()
十三、條件查詢
條件查詢通過如下3個類完成:
Criteria:代表一次查詢。
Criterion:代表一個查詢條件。
Restrictions:產(chǎn)生查詢條件的工具類。
執(zhí)行條件查詢的步驟如下:
(1)獲得Hibernate的Session對象。
(2)以Session對象創(chuàng)建Criteria對象。
(3)使用Restrictions的靜態(tài)方法創(chuàng)建Criterion查詢條件。
(4)向Criteria查詢中體檢Criterion查詢條件。
(5)執(zhí)行Criteria的list等方法返回結(jié)果集。
代碼片段如下:
List l = session.createCriteria(Student.class)
//此處增加限制條件必須是Student已經(jīng)存在的屬性
.add(Restrictions.get("studentNumber", 20050231L))
//如果要增加對Student的關(guān)聯(lián)類的屬性的限制
//則必須重新createCriteria()
//如果此關(guān)聯(lián)屬性是集合,則只要集合里任意一個對象的屬性滿足下面條件即可
.createCriteria("enrolments")
.add(Restrictions.gt("semester",2))
.list();
在條件查詢中,Criteria接口代表依次查詢,該查詢本身不具備任何的數(shù)據(jù)篩選功能,Session調(diào)用createCriterial(Class clazz)方法對某個持久化類創(chuàng)建條件查詢實例。
Criteria對象不具備任何的數(shù)據(jù)篩選功能,但程序可以通過向Criteria對象中組合多個Criterion(每個Criterion對象代表一個過濾條件)即可實現(xiàn)數(shù)據(jù)過濾了。
Criterion接口代表一個查詢條件,該查詢條件由Restrictions負(fù)責(zé)產(chǎn)生。Restrictions是專門用于產(chǎn)生查詢條件的工具類,它的方法大部分都是靜態(tài)方法,常用的方法如下:
(1)static Criteion allEq(Map propertyNameValues):判斷指定屬性(由Map參數(shù)的key指定)和指定值(由Map參數(shù)的value指定)是否完全相等。
(2)static Criterion between(String propertyName, Object lo, Object hi):判斷屬性值在某個指范圍之內(nèi)。
十四、關(guān)聯(lián)和動態(tài)關(guān)聯(lián)
代碼片段如下:
List l = session.createCriteria(Student.class)
//此處增加限制條件必須是Student已經(jīng)存在的屬性
.add(Restrictions.get("studentNumber", 20050231L))
//如果要增加對Student的關(guān)聯(lián)類的屬性的限制
//則必須重新createCriteria()
//如果此關(guān)聯(lián)屬性是集合,則只要集合里任意一個對象的屬性滿足下面條件即可
.createCriteria("enrolments")
.add(Restrictions.gt("semester",2))
.list();
上面的代碼表示建立Person類的條件查詢,第一個查詢條件是直接過濾Person的屬性。第二個查詢條件則過濾Person的關(guān)聯(lián)實體的屬性,其中enrolments是Person類的關(guān)聯(lián)實體,而semester則是Enrolment類的屬性。值得注意的是,返回的并不是Enrolment對象,而是Person對象的集合。
注意:使用關(guān)聯(lián)類的條件查詢,依然是查詢原有持久化類的實例,而不是查詢被關(guān)聯(lián)類的實例。
可使用條件查詢支持的替換形態(tài),將上面查詢代碼替換成如下形式:
List l = session.createCriteria(Student.class)
.add(Restrictions.gt("studentNumber",20050231L))
.createAlias("enrolments","en")
.add(Restrictions.gt("semester",2))
.list();
createAlias()方法并不創(chuàng)建一個新的Criteria實例,它只是給關(guān)聯(lián)實體(包含集合里包含的關(guān)聯(lián)實體)起一個別名,讓后面過濾條件可根據(jù)關(guān)聯(lián)實體進(jìn)行篩選。
在默認(rèn)情況下,條件查詢將根據(jù)映射文件指定的延遲加載策略來加載關(guān)聯(lián)實體,如果希望在條件查詢中改變延遲加載策略(就像在HQL查詢中使用fetch關(guān)鍵字一樣),那就可通過Creteria的setFetchMode()方法來實現(xiàn),該方法接受一個FetchMode參數(shù)。
FetchMode里有幾個常量,如下:
DEFAULT:使用配置文件制定的延遲加載策略處理。
JOIN:使用外連接,預(yù)初始化所有關(guān)聯(lián)實體。
SELECT:啟用延遲加載,系統(tǒng)將使用單獨(dú)的select語句來初始化關(guān)聯(lián)實體。只有當(dāng)個真正訪問關(guān)聯(lián)實體的時候,才會執(zhí)行第二條select語句。
初始化Student對象時,也可以初始化Student關(guān)聯(lián)的Enrolment實體,實體使用如下代碼:
List l = session.createCriteria(Student.class)
.add(Restrictions.gt("studentNumber",20050231L))
.setFetchMode("enrolments", FetchMode.JOIN)
.list();
十五、投影、聚合和分組
投影運(yùn)算實際上就是一個基于列的運(yùn)算,通常用于投影到指定列(也就是過濾其他列,類似select子句的作用),還可以完成SQL語句中常用的分組、組篩選等功能。
Hibernate的條件過濾中使用Projection代表投影運(yùn)算,Projection是一個接口,而Projections作為Projection的工廠,負(fù)責(zé)生成Projection對象。
一旦產(chǎn)生了Projection對象之后,就可通過Criteria提供的setProjection(Projection projection)方法來進(jìn)行投影運(yùn)算。從該方法上看,每個Criteria只能接受一個投影運(yùn)算,似乎無法進(jìn)行多個投影運(yùn)算,但實際上Hibernate又提供了一個ProjectionList類,該類是Projection的子類,并可以包含多個投影運(yùn)算,通過這種方式即完成多個投影運(yùn)算。
因此,一個條件查詢的投影運(yùn)算通常有如下程序結(jié)構(gòu):
List cats = session.createCriteria(Cat.class)
.setProjection(Projections.projectionList()
.add(Projections.rowCount())
.add(Projections.avg("weight"))
.add(Projections.groupProperty("color"))
).addOrder(Order.asc("color"))
.list();
注意:使用條件查詢的投影運(yùn)算時,不能使用顯示的分組子句,但某些投影類型的實質(zhì)就是分組投影,這些投影元素將出現(xiàn)在SQL的group by子句中——如上的groupProperty("color")投影。
投影運(yùn)算的實質(zhì)和group by子句、聚集函數(shù)的功能大致一致。
除此之外,如果我們希望對分組(投影)后屬性進(jìn)行排序,那就需要為投影運(yùn)算指定一個別名。為投影運(yùn)算指定別名有3種方法:
(1)使用Projections的alias()方法為指定投影指定別名。一旦為projection指定了別名,則程序就可以根據(jù)該P(yáng)rojection別名來進(jìn)行其他額外操作了,比如排序。條件查詢片段如下:
List l = session.createCriteria(Enrolment.class)
.setProjection(Projections.projectionList()
//按course進(jìn)行分組
.add(Projections.groupProperty("course"))
//統(tǒng)計記錄條數(shù),并為統(tǒng)計結(jié)果指定別名c
.add(Projections.alias(Projections.rowCount()," c"))
).addOrder(Order, asc("c"))
.list();
(2)使用SimpleProjection的as()方法為自身指定別名。
List l = session.createCriteria(Enrolment.class)
.setProjection(Projections.projectionList()
//按course進(jìn)行分組
.add(Projections.groupProperty("course").as("c"))
//統(tǒng)計記錄條數(shù),并為統(tǒng)計結(jié)果指定別名c
.add(Projections.rowCount())
).addOrder(Order, asc("c"))
.list();
(3)使用ProjectionList的add()方法添加投影時指定別名。
ProjectionList的add()方法有兩個重載形式,一個是直接添加投影,另一個是在添加投影時指定別名。條件查詢片段如下:
List l = session.createCriteria(Enrolment.class)
.setProjection(Projections.projectionList()
//按course進(jìn)行分組,指定別名為c
.add(Projections.groupProperty("course"),("c"))
//統(tǒng)計記錄條數(shù),并為統(tǒng)計結(jié)果指定別名rc
.add(Projections.rowCount(),"rc")
).addOrder(Order, asc("c"))
.list();
除此之外,Hibernate還提供了Property執(zhí)行投影運(yùn)算,Property投影的作用類似于SQL語句中的select,條件查詢的結(jié)果只有被Property投影的列才會被選出,條件查詢片段如下:
List l = session.createCriteria(Student.class)
.setProjection(Property.forName("name"))
.list();
上面條件查詢執(zhí)行的結(jié)果不再是Student對象集合,而是name屬性所組成的集合。
十六、離線查詢和子查詢
條件查詢的離線查詢由DetachedCriteria來代表,DetachedCriteria類使你在一個session范圍之外創(chuàng)建一個查詢,并且可以使用任意的Session來執(zhí)行它。
DetachedCriteria還可代表子查詢,當(dāng)我們把DetachedCriteria傳入Criteria中作為查詢條件時,DetachedCriteria就變成了子查詢。以下代碼片段所示:
//定義一個離線查詢
DetachedCriteria query = DetachedCriteria.forClass(Student.class)
.forClass(Student.class)
.setProjection(Property.forName("name"));
//打開Session和事務(wù)(省略)
//執(zhí)行離線查詢
List l = query.getExecutableCriteria(session).list();
//執(zhí)行子查詢
List l = session.createCriteria(Student.class)
.add(Property.forName("name").in(query))
.list();
如果程序使用Session的getExecutableCriteria()方法來執(zhí)行DetachedCriteria對象,則它被當(dāng)成離線查詢使用;如果程序使用Property的系列方法來操作DetachedCriteria對象,則它被當(dāng)成子查詢使用。
十七、事務(wù)和Session
1、Session和用戶請求是一對一得關(guān)系,這是一種理想的Session管理模式。
為了達(dá)到這種效果,推薦使用一個ThreadLocal變量,把Session綁定到處理客戶端請求的線程上。這種方式可以讓運(yùn)行在該線程上的所有程序代碼輕松地訪問Session。也可以在一個ThreadLocal變量中保持事務(wù)上下文環(huán)境,不過這依賴于你所選擇的數(shù)據(jù)庫事務(wù)劃分機(jī)制。這種實現(xiàn)模式被稱為ThreadLocal Session和Open Session in View。
工具類代碼如下:
public class HibernateUtil
{
//使用一個final變量來保存不可變的SessionFactory
public static final SessionFactory sessionFactory;
static{
try{
//采用默認(rèn)的Hibernate.cfg,xml來啟動一個Configuration的實例
Configuration conf = new Configuration().configure();
//由conf實例創(chuàng)建一個SessionFactory實例
sessionFactory = conf.buildSessionFactory();
}catch(Throwable ex){
System.err.println("初始化SessionFactory出現(xiàn)異常:"+ex);
throw new ExceptionInitializerError(ex);
}
}
//ThreadLocal是隔離多個線程的數(shù)據(jù)共享
//不存在多個線程之間共享資源,因此不再需要對線程同步
public static final ThreadLocal session = new ThreadLocal();
public static Session currentSession() throws HibernateException
{
Session s = (Session)session.get();
//如果該線程還沒有Session,則創(chuàng)建一個新的Session
if(s == null)
{
s = sessionFactory.openSession();
//將獲得的Session變量存儲在ThreadLocal變量session里
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException
{
Session s = (Session)session.get();
if(s != null)
{
s.close();
session.set(null);
}
}
}
在上面的代碼中,Hibernate Session被綁定到當(dāng)前線程。當(dāng)調(diào)用currentSession方法時,如果當(dāng)前線程中的Session已經(jīng)創(chuàng)建出來,那么將返回這個已經(jīng)存在的Session實例。
注意:幾乎所有情況下,都不要使用每個應(yīng)用對應(yīng)一次Hibernate Session的模式,也不要使用每次Http Session對應(yīng)一次Hibernate Session的模式。
2、對于以上的情況,Hibernate主要有如下3種模式來解決這個問題:
(1)自動版本化:Hibernate能夠自動進(jìn)行樂觀并發(fā)控制,如果在用戶思考的過程中持久化實體發(fā)生并發(fā)修改,Hibernate能夠自動檢測到。
(2)脫管現(xiàn)象:如果采用每次用戶請求對應(yīng)一次Session的模式,那么,前面載入的實例在用戶思考的過程中,始終與Session脫離,處于脫管狀態(tài)。Hibernate允許把脫管對象重新關(guān)聯(lián)到Session上,并且對修改進(jìn)行持久化。在這種模式下,自動版本化被用來隔離并發(fā)修改。這種模式也被稱為脫管對象的每次請求對應(yīng)一個Hibernate Session。
(3)長生命周期Session:Session可以在數(shù)據(jù)庫事務(wù)提交之后,斷開和底層的JDBC連接。當(dāng)新的客戶端請求到來時,它又重新連接上底層的JDBC鏈接。這種模式被稱為每個應(yīng)用程序事務(wù)對應(yīng)一個Session。因為應(yīng)用程序事務(wù)時相當(dāng)長(跨越多個用戶請求)的,所以也被稱為長生命周期Session。
注意:Session緩存了處于持久化狀態(tài)的每個對象(Hibernate會監(jiān)視和檢查臟數(shù)據(jù)),也就是說,如果程序讓Session打開很長一段時間,或者載入了很多數(shù)據(jù),Session占用的內(nèi)存會一直增長,直到拋出OutOfMemoryException異常。為了解決這個問題,程序定期調(diào)用Session的clear()和evict()方法來管理Session的緩存。對于一些大批量的數(shù)據(jù)處理,推薦使用DML風(fēng)格的HQL語句完成。
3、為了保證在Session關(guān)閉之前初始化代理屬性或集合屬性,程序可以Hibernate,initialized()靜態(tài)方法來強(qiáng)制初始化集合或代理。只要Session處于open狀態(tài),Hibernate.initialize(teahcer)將會強(qiáng)制初始化teacher代理,Hibernate.initialize(teacher.getStudents())對student的集合具有同樣的功能。
還有另一種選擇,就是程序讓Session一直處于打開狀態(tài),直到裝入所有需要的集合或代理。
保證Session處于打開狀態(tài)有兩種方法可以解決此問題:
(1)在一個Web應(yīng)用中,可以利用過濾器(Filter),在用戶請求結(jié)束、頁面生成結(jié)束時關(guān)閉Session。也就是保證在視圖層一直打開Session,這就是所謂的Open Session in View成視圖界面的過程中發(fā)生異常時,必須保證可以正確關(guān)閉Session,并結(jié)束事務(wù)。
(2)使用業(yè)務(wù)邏輯層來準(zhǔn)備數(shù)據(jù),在業(yè)務(wù)邏輯層返回數(shù)據(jù)之前,業(yè)務(wù)邏輯層對每個所需集合調(diào)用Hibernate.initialize()方法,或者使用帶fetch子句或FetchMethod.JOIN的查詢,事先取得所有數(shù)據(jù),并將這些數(shù)據(jù)封裝成VO(值對象)集合,然后程序可以關(guān)閉Session了。業(yè)務(wù)邏輯層VO集傳入視圖層,讓視圖層只負(fù)責(zé)簡單的顯示邏輯。在這種模式下,可以讓視圖層和Hibernate API徹底分離,保證視圖層不會出現(xiàn)持久層API,從而提供更好的解耦。
十七、上下文相關(guān)的Session
從Hibernate3.1開始,SessionFactory.getCurrentSession()的底層實現(xiàn)是可插拔的,Hibernate引入了CurrentSessionContext接口,并通過hibernate_current_session_context_class參數(shù)來管理上下文相關(guān)Session的底層實現(xiàn)。
CurrentSessionContext接口有如下3個實現(xiàn)類:
org.hibernate.context.JTASessionContext:根據(jù)JTA來跟蹤和界定上下文相關(guān)Session,這和最早的僅支持JTA的方法是完全一樣的。
org.hibernate.context.ThreadLocalSessionContext:通過當(dāng)前正在執(zhí)行的線程來跟蹤和界定上下文相關(guān)Session,也就是和前面的HibernateUtil的Session維護(hù)模式相似。
org.hibernate.context.ManagedSessionContext:通過當(dāng)前執(zhí)行的線程來跟蹤和界定上下文相關(guān)的Session。但是程序需要使用這個類的靜態(tài)方法將Session實例綁定、取消綁定,它并不會自動打開、flush或者關(guān)閉任何Session。
對于容器中使用Hibernate的場景而言,通常會采用第一種方式:對于獨(dú)立的Hibernate應(yīng)用而言,通常會采用第二種方式。
為了指定Hibernate使用哪種Session管理方式,可以在hibernate.cfg.xml文件中增加如下片段:
<property name="hibernate.current_session_context_class">thread</property>
如果在JTA事務(wù)環(huán)境中,則應(yīng)增加如下配置片段:
<property name="hibernate.current_session_context_class">jta</property>
對于第三種不常用的Session管理機(jī)制,則可在配置文件中簡寫成:managed。
posted on 2011-11-19 14:05
小熊寶貝的每一天 閱讀(4547)
評論(1) 編輯 收藏