2010-01-03 傳智播客—hibernate加強(qiáng)與二級(jí)緩存
Posted on 2010-01-03 23:17 長(zhǎng)城 閱讀(518) 評(píng)論(0) 編輯 收藏 從昨晚后半夜開(kāi)始下午,現(xiàn)在外面的雪還沒(méi)停,這是來(lái)北京后最大的一場(chǎng)雪。早上6:30起床,然后去吃早餐。今天是元旦假期的最后一天,幸好乘車(chē)的人不多,很快就坐上了車(chē)。如果是平時(shí)可能得在大雪里走幾站,甚至走到學(xué)校。氣溫還可以,在外面等車(chē)時(shí),雪好美!
今日繼續(xù)講解hibernate,也是hibernate課程的最后一天。Hibernate的內(nèi)容非常多,如果詳細(xì)著講估計(jì)還得兩天,但課程安排是三天。大部分之前的學(xué)生說(shuō)外面用hibernate的很少!~~ 無(wú)奈!繼續(xù)上一次課程…。
一、Hibernate的檢索策略
上一次課程我們學(xué)習(xí)了類(lèi)級(jí)別的檢索策略和關(guān)聯(lián)級(jí)別的一對(duì)多檢索策略,簡(jiǎn)單復(fù)習(xí)一下:
一對(duì)多關(guān)聯(lián)檢索:
Fecth lazy | True | false | extra |
Join | 迫左 | 迫左 | 迫左 |
Select | 延遲 | 立即 | 延遲(特別) |
subselect | | | |
Fecth的優(yōu)先級(jí)別高于lazy
延遲(特別):select count(id) from orders where cid=?
Subselect:使用嵌套子查詢,批量查詢的時(shí)候。
多對(duì)一關(guān)聯(lián)檢索:
Fecth lazy | false | proxy | No-proxy |
Join | 迫左 | 迫左 | 迫左 |
Select | 立即 | 延遲:對(duì)端類(lèi)級(jí)別是延遲 立即:對(duì)端類(lèi)級(jí)別是立即 | |
表單所屬的客戶,客戶是表單的一級(jí)關(guān)聯(lián),再取出客戶對(duì)應(yīng)的所有表單,此時(shí)表單是前一個(gè)表單的二級(jí)關(guān)聯(lián)。一級(jí)關(guān)聯(lián)、二級(jí)關(guān)聯(lián),兩次左外連接使用一條語(yǔ)句的話,會(huì)降低性能。所以使用了一條左邊連接查詢和一條查詢語(yǔ)句。這就是著名的N+1查詢。
hibernate3.0的所有檢索策略都為延遲
接下來(lái)我們繼續(xù)學(xué)習(xí)關(guān)聯(lián)級(jí)別的一對(duì)一檢索策略與多對(duì)多檢索策略。
1.一對(duì)一檢索策略
一對(duì)一關(guān)聯(lián)關(guān)系是一種什么樣的關(guān)系?一對(duì)一關(guān)系不可以把他們定義為一個(gè)類(lèi)嗎?把他們放在一個(gè)表里不可以嗎?當(dāng)然可以!
一對(duì)一關(guān)聯(lián)兩種方案:
1).外鍵關(guān)聯(lián),此種解決方案有兩種方法:
老徐舉的例子,一個(gè)用戶表對(duì)應(yīng)一個(gè)地址。為什么要這么做?假設(shè)地址中包含大字段,這樣可以減少資源的浪費(fèi),提高訪問(wèn)性能。
l 方法一、就是在多對(duì)一關(guān)聯(lián)的元素節(jié)點(diǎn)中添加一個(gè)unique=”true”屬性便形成了一對(duì)一關(guān)聯(lián)。
在用戶類(lèi)的映射文件中添加:
<!-- 影射一對(duì)一外鍵關(guān)聯(lián)用many-to-one進(jìn)行模擬,增加唯一性約束 --> <many-to-one name="addr" column="aid" class="AddrFk" unique="true" /> |
在地址類(lèi)的映射文件中添加:
<one-to-one name="user" property-ref="addr"/> |
l 方法二、直接使用一對(duì)一關(guān)系:
在用戶類(lèi)的映射文件中添加:
<!-- 影射一對(duì)一主鍵關(guān)聯(lián) --> <one-to-one name="addr" class="AddrPk" /> |
在地址類(lèi)的映射文件中添加:
<one-to-one name="user" class="UserPk" constrained="true"/> |
同時(shí)我們需要設(shè)置地址類(lèi)映射文件的主鍵為:
<id name="id" column="id" type="integer"> <generator class="foreign"> <param name="property">user</param> </generator> </id> |
將用戶表的主鍵設(shè)置為地址表的外鍵。
2).主鍵關(guān)聯(lián),此時(shí)子表的主鍵與主表的主鍵一一對(duì)應(yīng)。有三種解決方案:
老徐舉了一個(gè)例子:?jiǎn)T工有鐘點(diǎn)工和普通工人之分,鐘點(diǎn)工領(lǐng)取的是時(shí)效工資(rate),普通工人領(lǐng)取的是月薪(salary)。所以我們有必要提取一個(gè)員工的超類(lèi),并分別實(shí)現(xiàn)鐘點(diǎn)工和普通工人的子類(lèi)。
l 方法一、我們將鐘點(diǎn)工和普通工人的信息放到一個(gè)表里,需要在映射文件中添加:
<!-- 區(qū)分符 --> <discriminator column="etype" type="string" length="2" /> <property name="name" column="name" type="string" length="15" /> <!-- 子類(lèi)(整個(gè)繼承關(guān)系樹(shù)對(duì)應(yīng)于一個(gè)表) --> <subclass name="HeSingle" discriminator-value="he" lazy="false"> <property name="rate" column="rate" type="float" /> </subclass> <subclass name="SeSingle" discriminator-value="se" lazy="false"> <property name="salary" column="salary" type="float" /> </subclass> |
注意“discriminator”元素用于定義一個(gè)額外的列,用于區(qū)分員工的類(lèi)型。
l 方法二、我們?yōu)槊款?lèi)員工都分別建一個(gè)表,如:
<!-- 每個(gè)子類(lèi)對(duì)應(yīng)一個(gè)表,從表和主表間一對(duì)一關(guān)系 --> <joined-subclass name="HeJoined" table="hib_hejoineds" lazy="false"> <key column="eid" /> <property name="rate" column="rate" type="float" /> </joined-subclass> <joined-subclass name="SeJoined" table="hib_sejoineds" lazy="false"> <key column="eid" /> <property name="salary" column="salary" type="float" /> </joined-subclass> |
“column="eid"”被做為外鍵對(duì)應(yīng)主表的員工id。
l 方法三、為每類(lèi)員工建立一個(gè)表,他們與主表無(wú)關(guān)各自具有全字段。但是這三個(gè)表所使用的Id是不重復(fù)的:
<union-subclass name="HeUnion" table="hib_heunions"> <property name="rate" column="rate" type="float" /> </union-subclass> <union-subclass name="SeUnion" table="hib_seunions"> <property name="salary" column="salary" type="float" /> </union-subclass> |
此時(shí),我們需要使用一個(gè)特殊的主鍵增值生成器:
<id name="id" column="id" type="integer"> <generator class=" <param name="table">hib_hilo</param> <param name="column">currvalue</param> <param name="max_lo">10</param> </generator> </id> |
“"max_lo"”是id取值區(qū)間,每次插入記錄時(shí),生成器會(huì)根據(jù)此值步長(zhǎng)增長(zhǎng)。如果我們執(zhí)行的一次插入操作,插入了10條記錄。那么就需要將此設(shè)置為10,以便生成器自動(dòng)為我們生成10個(gè)不同的id。
2.多對(duì)多檢索策略
在以前的學(xué)習(xí)中,我們知道多對(duì)多關(guān)聯(lián)關(guān)系需要一個(gè)中間表,用于記錄兩個(gè)多對(duì)多表的對(duì)應(yīng)主鍵。反應(yīng)到映射文件中,我們需要為兩個(gè)類(lèi)添加set元素。我們依然使用,老師與學(xué)生的例子,一個(gè)老師可以有多個(gè)學(xué)生,一個(gè)學(xué)生可以有多個(gè)老師,映射文件如下:
老師類(lèi)映射文件中的set元素:
<set name="stus" table="hib_tea_stu_links" lazy="false"> <key column="tid" /> <many-to-many class="Stu" column="sid" /> </set> |
“column="tid"”對(duì)應(yīng)中間表“hib_tea_stu_links”的老師外鍵。“column="sid"”對(duì)應(yīng)中間表“hib_tea_stu_links”學(xué)生的外鍵。
學(xué)生類(lèi)映射文件中的set元素:
<set name="teas" table="hib_tea_stu_links" lazy="false" inverse="true"> <key column="sid" /> <many-to-many class="Tea" column="tid" /> </set> |
檢索策略 | 優(yōu)點(diǎn) | 缺點(diǎn) | 優(yōu)先考慮使用的場(chǎng)合 |
立即檢索 | 對(duì)應(yīng)用程序完全透明,不管對(duì)象處于持久化狀態(tài)還是游離狀態(tài),應(yīng)用程序都可以從一個(gè)對(duì)象導(dǎo)航到關(guān)聯(lián)的對(duì)象 | (1)select語(yǔ)句多 (2)可能會(huì)加載應(yīng)用程序不需要訪問(wèn)的對(duì)象,浪費(fèi)許多內(nèi)存空間。 | (1)類(lèi)級(jí)別 (2)應(yīng)用程序需要立即訪問(wèn)的對(duì)象 (3)使用了二級(jí)緩存 |
延遲檢索 | 由應(yīng)用程序決定需要加載哪些對(duì)象,可以避免執(zhí)行多余的select語(yǔ)句,以及避免加載應(yīng)用程序不需要訪問(wèn)的對(duì)象。因此能提高檢索性能,并節(jié)省內(nèi)存空間。 | 應(yīng)用程序如果希望訪問(wèn)游離狀態(tài)的代理類(lèi)實(shí)例,必須保證她在持久化狀態(tài)時(shí)已經(jīng)被初始化。 | (1)一對(duì)多或者多對(duì)多關(guān)聯(lián) (2)應(yīng)用程序不需要立即訪問(wèn)或者根本不會(huì)訪問(wèn)的對(duì)象 |
迫切左外連接檢索 | (1)對(duì)應(yīng)用程序完全透明,不管對(duì)象處于持久化狀態(tài)還是游離狀態(tài),都可從一個(gè)對(duì)象導(dǎo)航到另一個(gè)對(duì)象。 (2)使用了外連接,select語(yǔ)句少 | (1)可能會(huì)加載應(yīng)用程序不需要訪問(wèn)的對(duì)象,浪費(fèi)內(nèi)存。 (2)復(fù)雜的數(shù)據(jù)庫(kù)表連接也會(huì)影響檢索性能。 | (1)多對(duì)一或一對(duì)一關(guān)聯(lián) (2)需要立即訪問(wèn)的對(duì)象 (3)數(shù)據(jù)庫(kù)有良好的表連接性能。 |
二、Hibernate的檢索方式
Hibernate的檢索方式:
1.對(duì)象導(dǎo)航圖:根據(jù)映射文件檢索
2.OID檢索:根據(jù)OID檢索
3.HQL檢索:一種類(lèi)似于SQL語(yǔ)句的面向?qū)ο蟮?/span>hibernate檢索文本語(yǔ)句
4.QBC檢索:相當(dāng)于將HQL分解為一個(gè)個(gè)對(duì)象的檢索方式
5.本地SQL檢索:SQL語(yǔ)句
只有HQL和QBC我們比較陌生,在此我們介紹一下:
1.HQL語(yǔ)句
session.createQuery("from Customer c where c.name = 'itcast' and c.age = 12"); |
Customer:指向類(lèi)名
c.name:為映射文件中property元素的name
c.ge:為映射文件中property元素的age
看到?jīng)]有,HQL與數(shù)據(jù)庫(kù)毫無(wú)關(guān)系。HQL操作的持久化類(lèi)的映射文件。
HQL有三種查詢方式:
1).使用參數(shù)名,如:
//綁定參數(shù)(按照參數(shù)名稱綁定) q = s.createQuery("from Customer c where c.name = :a and c.age = :b"); q.setString("a","itcast"); q.setInteger("b",12); q.list(); |
2).使用索引
//綁定參數(shù)(按照參數(shù)索引位置綁定) q = s.createQuery("from Customer c where c.name = ? and c.age = ?"); q.setString(0,"itcast"); q.setInteger(1,12); q.list(); |
3).命名查詢
我們需要在映射文件中添加一個(gè)class的兄弟元素,這個(gè)元素可以添加在任意相關(guān)的映射文件中:
<!-- 命名查詢 --> <query name="findCustomerByName"> <![CDATA[from Customer c where c.name = ?]]> </query> |
我們?cè)诔绦蛑兄苯诱{(diào)用:
//命名查詢(將查詢條件定義到影射文件中,從影射文件中提取查詢條件) q = s.getNamedQuery("findCustomerByName"); q.setString(0, "t,om"); q.list(); |
4).特別的左外連接查詢:
//左外連接查詢,集合中的每個(gè)元素都是對(duì)象數(shù)組 s.createQuery("from Customer c left outer join c.orders").list(); //迫切左外連接查詢,集合中的每個(gè)元素都是客戶對(duì)象 list = s.createQuery("from Customer c left outer join fetch c.orders").list(); Set<Customer> customers = new HashSet<Customer>(list); |
左外連接查詢返回值:list中每個(gè)元素都是一個(gè)具有兩個(gè)成員的數(shù)組。數(shù)組[0]是Customer對(duì)象,數(shù)組[1]是Order對(duì)象。
迫切左外連接返回值:list中每個(gè)元素都是一個(gè)Customer對(duì)象。
2.QBC語(yǔ)句
我們將上面的HQL語(yǔ)句,使用QBC來(lái)實(shí)現(xiàn):
Criteria cra = s.createCriteria(Customer.class); Criterion ctn_name = Restrictions.eq("name", "itcast"); Criterion ctn_age = Restrictions.eq("age", 12); //調(diào)用list時(shí)執(zhí)行查詢并返回值 list = cra.add(ctn_name).add(ctn_age).list(); |
QBC語(yǔ)句,一般用于動(dòng)態(tài)查詢。比如,WEB應(yīng)用中的高級(jí)搜索功能!
上面介紹的都是相對(duì)簡(jiǎn)單的查詢。至于復(fù)雜的查詢,我想玩過(guò)數(shù)據(jù)庫(kù)的人一想就明白了。
比較方面 | HQL檢索 | QBC檢索 |
可讀性 | 優(yōu)點(diǎn):和sql相近,易讀 | 將語(yǔ)句肢解成一組criteria,較差 |
功能 | 支持各種查詢 | 不支持報(bào)表查詢和子查詢。有限的連接查詢 |
查詢語(yǔ)句形式 | 基于字符串形式的sql | 更加面向?qū)ο?/span> |
何時(shí)被解析 | 運(yùn)行時(shí)被解析 | 編譯時(shí)被解析,更易排錯(cuò) |
可擴(kuò)展性 | 不具擴(kuò)展性 | 用戶可擴(kuò)展criteria接口 |
對(duì)動(dòng)態(tài)查詢語(yǔ)句的支持 | 支持動(dòng)態(tài)查詢,編程麻煩 | 適合動(dòng)態(tài)生成查詢語(yǔ)句 |
三、Hibernate的映射繼承關(guān)系
就是前邊檢索策略中提到的:
joined-subclass、union-subclass、subclass這些元素。
四、Hibernate批量操作
使用HQL和QBC等查詢語(yǔ)句進(jìn)行批量操作…
五、Hibernate的緩存
二級(jí)緩存位于SessionFactory中,用于進(jìn)程范圍:多個(gè)工作單元共享,可并發(fā)訪問(wèn),可存儲(chǔ)實(shí)例本身也可存散列數(shù)據(jù),然后在重新組裝成對(duì)象放到一級(jí)緩存中。集群范圍:多個(gè)進(jìn)程和主機(jī)間訪問(wèn),網(wǎng)絡(luò)通信是重點(diǎn)。需要將數(shù)據(jù)復(fù)制到所有集群中的節(jié)點(diǎn)。
1. Hibernate緩存基礎(chǔ)
緩存不是JPA和EJB規(guī)范,用于性能優(yōu)化,不同廠商方案不同。
緩存維護(hù)數(shù)據(jù)狀態(tài)在本地,內(nèi)存或者磁盤(pán)。
緩存會(huì)減少數(shù)據(jù)庫(kù)訪問(wèn)。
2.緩存策略與范圍
緩存類(lèi)型
一.事務(wù)范圍:位于當(dāng)前工作單元,不能并發(fā)訪問(wèn)。
二.進(jìn)程范圍:多個(gè)工作單元共享,可并發(fā)訪問(wèn),可存儲(chǔ)實(shí)例本身也可存散列數(shù)據(jù),然后在 重新組裝。
三.集群范圍:多個(gè)進(jìn)程和主機(jī)間訪問(wèn),網(wǎng)絡(luò)通信是重點(diǎn)。需要將數(shù)據(jù)復(fù)制到所有集群中的節(jié)點(diǎn)。
緩存和OID:
事務(wù)級(jí)緩存也用于對(duì)象id的使用范圍,是理想的緩存。
進(jìn)程級(jí)緩存可選擇實(shí)現(xiàn)id的進(jìn)程范圍存儲(chǔ),也和主鍵對(duì)應(yīng)。并發(fā)工作單元查詢同一id對(duì)象的話返 回相同的實(shí)例。在進(jìn)程級(jí)緩存中的對(duì)象也可按值返回,每個(gè)工作單元再重新組裝形成副本。
一級(jí)緩存是強(qiáng)制的,他也維護(hù)了OID,二級(jí)緩存(進(jìn)程級(jí)/集群級(jí))對(duì)某些數(shù)據(jù)來(lái)說(shuō)是有用的。
3.緩存的架構(gòu)
1、一級(jí)緩存即session。
2、二級(jí)緩存是可配的插件,可用于進(jìn)程/集群范圍緩存。他們緩存都是狀態(tài)(按值返回),而不是真正的持久化對(duì)象。對(duì)于特定的數(shù)據(jù)項(xiàng)來(lái)說(shuō)緩存的并發(fā)策略定義了事務(wù)的隔離細(xì)節(jié)。每個(gè)類(lèi)或者每個(gè)集合的二級(jí)緩存是可選可配的。每個(gè)緩存都使用了自己的緩存區(qū)域。
3、Hibernate還實(shí)現(xiàn)了對(duì)查詢結(jié)果集的緩存,他和二級(jí)緩存緊密結(jié)合.而且需要額外的兩個(gè)物理緩存區(qū)域來(lái)容納緩存的查詢結(jié)果和最后更新表的時(shí)間戳。
1).Hibernate二級(jí)緩存
1、所有通過(guò)同一SessionFactory開(kāi)啟的會(huì)話共享同一二級(jí)緩存。
2、對(duì)象以拆解的形式存于二級(jí)緩存中(拆解是串行化過(guò)程,算法更多,更快比java串行)。
3、重點(diǎn)在于緩存方案(緩存策略與物理緩存提供商)。
4、不同數(shù)據(jù)需要不同的緩存方案。涉及如下設(shè)置:
a、是否開(kāi)啟二級(jí)緩存
b、Hibernate并發(fā)策略
c、緩存過(guò)期策略(timerout LRU 內(nèi)存敏感)
d、緩存的物理格式(內(nèi)存 索引文件 集群替換)
二級(jí)緩存安裝需要兩步:
1、決定使用哪個(gè)緩存并發(fā)策略
2、配置緩存過(guò)期和物理緩存屬性(cache provider)
2).內(nèi)置并發(fā)策略
并發(fā)策略是調(diào)解人,負(fù)責(zé)在緩存中檢索數(shù)據(jù)。對(duì)于特定數(shù)據(jù)項(xiàng),他也定義了事務(wù)隔離的語(yǔ)義。對(duì)類(lèi) 或集合來(lái)說(shuō)使用哪個(gè)并發(fā)策略需要做出判斷。
四個(gè)內(nèi)置的并發(fā)策略體現(xiàn)了遞減的事務(wù)隔離的等級(jí)。
1、Transationsal
只在受管環(huán)境中使用,如果需要可以確保所有的事務(wù)隔離到可重復(fù)讀,很少更新且防止臟數(shù) 據(jù)情況下該策略很重要
2、Read-write
該策略維護(hù)讀已提交隔離級(jí)別,使用了時(shí)間戳機(jī)制只在非集群環(huán)境下使用。
3、Nostrict-read-write
不保證在數(shù)據(jù)庫(kù)和緩存之間數(shù)據(jù)的一致性,如果使用的話,應(yīng)該配置一個(gè)高效短期的過(guò)期超 時(shí)。否則,可能讀到臟數(shù)據(jù)。
4、Read-only
適用于從不發(fā)生改變的數(shù)據(jù)。只對(duì)數(shù)據(jù)進(jìn)行引用。約束的降低帶來(lái)了性能提升。
還可以實(shí)現(xiàn)自定義策略:
org。hibernate。cache。CacheConcurrencyStrategy
3). 選擇緩存供應(yīng)商
Hibernate要求必須為整個(gè)應(yīng)用選擇一個(gè)供應(yīng)商。以下是開(kāi)源的實(shí)現(xiàn):
1、EHCache
適用于單個(gè)虛擬機(jī)中單個(gè)進(jìn)程范圍,可緩存至內(nèi)存和磁盤(pán),支持查詢緩存(最新版本支持集群
緩存,未測(cè)試)
2、OpenSymphony OSCache
在單個(gè)虛擬機(jī)中緩存至內(nèi)存或磁盤(pán)。支持豐富的過(guò)期策略和查詢緩存。
3、SwarmCache
基于JGROUPS的集群緩存, 不支持Hibernate查詢緩存
4、JBoss Cache
同樣基于JGROUPS廣播庫(kù)的完全事務(wù)性自我復(fù)制集群緩存,支持自我復(fù)制、校驗(yàn)、同/異步
通信、樂(lè)觀/ 悲觀鎖
二級(jí)緩存講的有些快,內(nèi)部實(shí)現(xiàn)原理沒(méi)講,因?yàn)闀r(shí)間來(lái)不急了。至于二級(jí)緩存的詳細(xì)配置與實(shí)現(xiàn)有的是以后有時(shí)間再自學(xué)吧!
今天的日志主要使用了老徐的資料,在檢索策略那塊還好,因?yàn)橹v的比較細(xì)。之后的主要就是緩存技術(shù),一級(jí)緩存學(xué)的挺好,就這個(gè)二級(jí)緩存留有些“懸念”。
老徐是一位優(yōu)秀的老師,傳智播客確實(shí)很牛。能留住這么多人才!
明天就開(kāi)始學(xué)習(xí)luncene了,期待它的到來(lái)!
加油!