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