目錄
寫在前面
上篇文章介紹了nhibernate中一級緩存的相關內容,一級緩存過期時間和ISession對象的生命周期相同,並且不同的Session不能共享緩存,一級緩存也可以成為ISession緩存。那么現在我們就學一下nhibernate中的二級緩存,即ISessionFactory級別緩存,可被所有的ISession所共享。二級緩存是可擴展的,在http://sourceforge.net/projects/nhcontrib/上提供了第三方的Nhibernate二級緩存提供程序。
文檔與系列文章
[NHibernate]持久化類(Persistent Classes)
[NHibernate]集合類(Collections)映射
[NHibernate]緩存(NHibernate.Caches)
[NHibernate]NHibernate.Tool.hbm2net
[NHibernate]Nhibernate如何映射sqlserver中image字段
[NHibernate]條件查詢Criteria Query
[Nhibernate]SchemaExport工具的使用(一)——通過映射文件修改數據表
[Nhibernate]SchemaExport工具的使用(二)——創建表及其約束、存儲過程、視圖
二級緩存
關於二級緩存的詳細可以參考[NHibernate]緩存(NHibernate.Caches)。
NHibernate session有一個內部的(一級)緩存,存放着它的實體。這些緩存沒有共享,因此session被銷毀時它的緩存也被銷毀了。NHibernate提供了二級緩存系統;它在SessionFactory級別工作。因此它被同一個SessionFactory產生的session共享。
在NHibernate中,當我們啟用NHibernate二級緩存。
使用ISession進行數據操作時,NHibernate首先從內置緩存(一級緩存)中查找是否存在需要的數據,如果內置緩存不存在需要的數據,則查詢二級緩存,如果二級緩存中存在所需數據,則直接使用緩存中數據,否則從數據庫中查詢數據並放入緩存中。
NHibernate本身提供了一個基於Hashtable的HashtableCache緩存,但是功能非常有限而且性能比較差,不適合在大型應用程序使用,我們可以使用第三方緩存提供程序作為NHibernate二級緩存實現。
使用緩存的缺點:
如果緩存策略設置不當,NHibernate不知道其它應用程序對數據庫的修改及時更新緩存。因此,建議只對系統經常使用、數據量不大且不會被其它應用程序修改的只讀數據(或很少被修改的數據)使用緩存。
Nhibernate二級緩存提供程序
NHibernate提供了NHibernate.Cache.ICacheProvider接口用來支持第三方緩存提供程序實現。開發緩存提供程序時,需要實現該接口作為NHibernate和緩存實現直接的適配器。NHibernate提供了常見的緩存提供程序的內置適配器,這些適配器都實現了NHibernate.Cache.ICacheProvider接口。
NHibernate.Cache.ICacheProvider定義如下:

1 namespace NHibernate.Cache 2 { 3 // 摘要: 4 // Support for pluggable caches 5 public interface ICacheProvider 6 { 7 // 摘要: 8 // Configure the cache 9 // 10 // 參數: 11 // regionName: 12 // the name of the cache region 13 // 14 // properties: 15 // configuration settings 16 ICache BuildCache(string regionName, IDictionary<string, string> properties); 17 // 18 // 摘要: 19 // generate a timestamp 20 long NextTimestamp(); 21 // 22 // 摘要: 23 // Callback to perform any necessary initialization of the underlying cache 24 // implementation during ISessionFactory construction. 25 // 26 // 參數: 27 // properties: 28 // current configuration settings 29 void Start(IDictionary<string, string> properties); 30 // 31 // 摘要: 32 // Callback to perform any necessary cleanup of the underlying cache implementation 33 // during NHibernate.ISessionFactory.Close(). 34 void Stop(); 35 } 36 }
除了NHibernate本身提供的一個基於Hashtable的HashtableCache緩存。
在NHibernate Contrib上提供了六種第三方NHibernate二級緩存提供程序,完全開源的。我們直接下載其程序集引用到我們的項目中就可以使用了。
- NHibernate.Caches.MemCache
- NHibernate.Caches.Prevalence
- NHibernate.Caches.SharedCache
- NHibernate.Caches.SysCache
- NHibernate.Caches.SysCache2
- NHibernate.Caches.Velocity
一個例子
如何使用?
在默認情況下,NHibernate不啟動二級緩存。如果要使用二級緩存則需要在NHibernate配置文件中顯式的啟用二級緩存。NHibernate二級緩存可以分別為每一個具體的類和集合配置應用級或分布式緩存。
緩存並發策略
當兩個獨立的事務同時訪問數據庫時,可能產生丟失更新、不可重復讀等並發問題。同樣,當兩個並發事務同時訪問緩存時,也有可能產生各種並發問題。因此,在緩存級別也需要設置相應的並發訪問策略。
NHibernate內置四種並發訪問策略:
- read-only:只讀緩存。適用於只讀數據。可用於群集中。
- read-write:讀寫緩存。
- nonstrict-read-write:非嚴格讀寫緩存。不保證緩存與數據庫的一致性。
- transactional:事務緩存。提供可重復讀的事務隔離級別。
配置緩存
在NHibernate配置文件中通過cache.provider_class屬性顯式指定緩存實現,屬性值為緩存適配器的具體類名。如果你使用上面的第三方緩存提供程序,還需要配置緩存提供程序本身,關於第三方緩存提供程序的配置可以參考文檔[NHibernate]緩存(NHibernate.Caches)。首先是從Nhibernate本身自帶的HashtableCache緩存入手,學習一下緩存如何配置。
在nhibernate配置文件中顯示指定緩存提供者類
1 <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property>
1 <!--顯示啟用二級緩存,用cache.use_second_level_cache屬性顯式啟用二級緩存,參數為Bool值,這里啟用設置為true--> 2 <property name="cache.use_second_level_cache">true</property>
如果使用第三方緩存提供程序,那么需要對第三方緩存提供程序本身進行配置,需要詳細配置第三方緩存提供程序緩存屬性:保存時間、過期時間、可以緩存對象數量。
為每一個持久化類和集合指定相應的緩存策略
方法一:在映射文件中通過<cache>元素配置類和集合的緩存策略,在Class元素或者集合元素中添加<cache>元素進行配置。注意:<cache>元素必須在<id>元素之前。
1 <cache usage="read-only|read-write|nonstrict-read-write" region="默認類或集合名稱"/>
方法二:在NHibernate配置文件hibernate.cfg.xml中通過<class-cache>元素和<collection-cache>元素分別配置類和集合的緩存策略。
1 <class-cache class="類名稱" region="默認類名稱" include="all|non-lazy" 2 usage="read-only|read-write|nonstrict-read-write|transactional" />
<!--指定集合--> <collection-cache collection ="集合名稱" region="默認集合名稱" usage="read-only|read-write|nonstrict-read-write|transactional"/>
屬性含義
- region:可選,默認值為類或集合的名稱,用來指定二級緩存的區域名,對應於緩存實現的一個命名緩存區域。
- include:可選,默認值為all,當取non-lazy時設置延遲加載的持久化實例的屬性不被緩存。
- usage:聲明緩存同步策略,就是上面說明的四種緩存策略。
有兩種方式定義緩存策略,到底選擇那種好?
在nhibernate配置文件中你可以為每個類設置更方便,維護起來也方便,如果在每個持久化類中分別設置緩存策略,維護起來有點麻煩。
測試
在nhibernate中啟用二級緩存,並指定緩存Customer類
1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" > 3 <session-factory> 4 <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property> 5 <property name="connection.connection_string"> 6 server=.;database=shop;uid=sa;pwd=sa 7 </property> 8 <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property> 9 <property name="show_sql">true</property> 10 <!--二級緩存配置--> 11 <!--cache.provider_class屬性顯式指定緩存實現,屬性值為緩存適配器的具體類名--> 12 <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property> 13 <!--顯示啟用二級緩存,用cache.use_second_level_cache屬性顯式啟用二級緩存,參數為Bool值,這里啟用設置為true--> 14 <property name="cache.use_second_level_cache">true</property> 15 <!--啟用查詢緩存--> 16 <property name ="cache.use_query_cache">true</property> 17 <mapping assembly="Wolfy.Shop.Domain"/> 18 <!--Customer類啟用二級緩存--> 19 <class-cache class="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" usage="read-write"/> 20 </session-factory> 21 </hibernate-configuration>
Customer.hbm.xml映射文件

1 <?xml version="1.0" encoding="utf-8" ?> 2 <!--assembly:程序集,namespace:命名空間--> 3 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Wolfy.Shop.Domain" namespace="Wolfy.Shop.Domain.Entities"> 4 <!--存儲過程--> 5 <class name="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" table="TB_Customer"> 6 <!--二級緩存--> 7 <cache usage="read-write"/> 8 <!--主鍵--> 9 <id name="CustomerID" type="Guid" unsaved-value="null"> 10 <column name="CustomerID" sql-type="uniqueidentifier" not-null="true" unique="true" /> 11 <generator class="assigned"></generator> 12 </id> 13 <!--版本控制--> 14 <version name="Version" column="Version" type="integer" unsaved-value="0"/> 15 <!--組件 name組件屬性名--> 16 <component name="NameAddress" class="Wolfy.Shop.Domain.Entities.Name,Wolfy.Shop.Domain"> 17 <!--Name類中的屬性property--> 18 <property name="CustomerName" column ="CustomerName" type="string" 19 length="16" not-null="false" /> 20 <property name ="CustomerAddress" column="CustomerAddress" type="string" 21 length="128" not-null="false" /> 22 </component> 23 <!--一對多關系:一個客戶可以有一個或者多個訂單--> 24 <!--子實體負責維護關聯關系--> 25 <set name="Orders" table="TB_Order" generic="true" inverse="true" cascade="all"> 26 <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key> 27 <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/> 28 </set> 29 <!--存儲過程,check參數:none/rowcount/param--> 30 <sql-insert>exec TB_CustomerInsert ?,?,?,?</sql-insert> 31 <!--<sql-update>exec TB_CustomerUpdate ?,?,?,?</sql-update>--> 32 <sql-update>UPDATE TB_CustomerUpdate SET Version=?, [CustomerName]=?,[CustomerAddress]=? WHERE CustomerID=? AND Version=? </sql-update> 33 <!--<sql-delete check="rowcount" >exec TB_CustomerDelete ?</sql-delete>--> 34 <sql-delete>DELETE FROM [TB_Customer] WHERE [CustomerID] = ? and [Version] =?</sql-delete> 35 </class> 36 <!--需要和class節點同一級別--> 37 <sql-query name="ps_Search" > 38 <!--<return class="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" />--> 39 <return-scalar column="CustomerName" type="String"/> 40 exec ps_Search :CustomerID 41 </sql-query> 42 </hibernate-mapping>
在不同的Session中查詢實體
1 /// <summary> 2 /// 根據客戶id查詢 3 /// </summary> 4 /// <param name="customerID"></param> 5 /// <returns></returns> 6 public Customer GetCustomerById(Guid customerID) 7 { 8 ISession session = NHibernateHelper.GetSession(); 9 return session.Get<Customer>(customerID); 10 } 11 /// <summary> 12 /// 根據客戶id查詢 13 /// </summary> 14 /// <param name="customerID"></param> 15 /// <returns></returns> 16 public Customer GetCustomerById2(Guid customerID) 17 { 18 //重置Session 19 ISession session = NHibernateHelper.ResetSession(); 20 return session.Get<Customer>(customerID); 21 }
單元測試
1 [TestMethod] 2 public void GetCustomerById2Test() 3 { 4 Console.WriteLine("Session1 第一次加載"); 5 Customer c1 = _customerData.GetCustomerById(new Guid("DDF63750-3307-461B-B96A-7FF356540CB8")); 6 Assert.IsNotNull(c1); 7 Console.WriteLine("Session2 第二次加載"); 8 Customer c2 = _customerData.GetCustomerById2(new Guid("DDF63750-3307-461B-B96A-7FF356540CB8")); 9 Assert.IsNotNull(c2); 10 }
在第一次查詢數據時,由於一級、二級緩存中都不存在需要的數據,這時NHibernate從數據庫中查詢數據。第二次讀取同一數據,NHibernate首先從內置緩存(一級緩存)中查找是否存在所需要數據,由於不是在同一個ISession中,所以內置ISession緩存中不存在所需數據,NHibernate則查詢二級緩存,這時由於第一次查詢了這條數據,所以在二級緩存中存在所需數據,則直接使用緩存中數據。(該測試方法與一級緩存中的測試方法相同,為了方便對比,將上篇文章中的圖貼在一起進行對比)
一級緩存測試結果
二級緩存測試結果
通過對比,可以發現在單元測試中第二次會話,斷言c2不為null通過了測試,說明c2是從緩存中取的。也印證了ISessionFactory級別的二級緩存是可以共享緩存的。
總結
在學習nhibernate過程中難免遇到各種各樣的錯誤,學習的過程也是解決各種異常的過程。
關於二級緩存中簡單的查詢緩存就介紹到這里,下篇文章將介紹二級緩存針對刪除,修改的策略及緩存的管理的內容。
參考文章