NHibernate二級快取(第十一篇)
一、NHibernate二級快取簡介
NHibernate由ISessionFactory建立,可以被所有的ISession共享。
注意NHibernate查詢快取的順序,在使用ISession操作資料時,NHibernate會先從一級快取中查詢需要的資料,如果一級快取不存在需要的資料,則查詢二級快取,如果二級快取存在所需資料,則直接使用快取中的資料。如果二級快取都沒有,那麼才執行SQL語句,從資料庫中查詢快取。 NHibernate本身提供了一個基於Hashtable的HashtableCache快取,不過功能有限且效能不高。不適合用於大型應用程式,不過我們可以使用第三方快取提供程式作為NHibernate二級快取實現。
啟用二級快取
NHibernate預設是不開啟二級快取的,要開啟NHibernate要在配置屬性中配置如下三個屬性:
<!-- 配置二級快取實現程式 --> <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property>` <!-- 開啟二級快取 --> <property name="cache.use_second_level_cache">true</property> <!-- 在查詢中開啟二級快取 -->`<property name="cache.use_query_cache">true</property>`
NHibernate提供了六種第三方二級快取提供程式。都是開源的。
- NHibernate.Caches.MemCache
- NHibernate.Caches.Prevalence
- NHibernate.Caches.SharedCache
- NHibernate.Caches.SysCache
- NHibernate.Caches.SysCache2
- NHibernate.Caches.Velocity
快取策略
快取策略可以在配置檔案中指定,也可以在每一個對映檔案中指定,建議儘量在配置檔案中指定。這樣不用兼顧那麼多的對映檔案。
指定類:
<class-cache class="類名稱" region="預設類名稱" include="all|non-lazy" 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:宣告快取同步策略,就是上面說明的四種快取策略。
讀寫快取策略的說明:
- read-only:只讀快取。適用於只讀資料。可用於群集中。
- read-write:讀寫快取。
- nonstrict-read-write:非嚴格讀寫快取。不保證快取與資料庫的一致性。
- transactional:事務快取。提供可重複讀的事務隔離級別。
查詢二級快取配置:
-
Cacheable 為一個查詢顯示啟用二級快取;
-
CacheMode 快取模式, 有如下可選:
- Ignore:更新資料時將二級快取失效,其它時間不和二級快取互動
- Put:向二級快取寫資料,但不從二級快取讀資料
- Get:從二級快取讀資料,僅在資料更新時向二級快取寫資料
- Normal:預設方式。從二級快取讀/寫資料
- Refresh:向二級快取寫資料,想不從二級快取讀資料,通過在配置檔案設定 cache.use_minimal_puts從資料庫中讀取資料時,強制二級快取重新整理
-
CacheRegion 給查詢快取指定了特定的命名快取區域, 如果兩個查詢相同, 但是指定的 CacheRegion 不同, 則也會從資料庫查詢資料。
二、NHibernate二級快取的實現
配置檔案App.Config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> </configSections> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property> <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> <property name="show_sql">true</property> <property name="connection.connection_string"> Server=KISSDODOG-PC;initial catalog=Test;uid=sa;pwd=123; </property> <!-- 配置二級快取實現程式 --> <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property> <!-- 開啟二級快取 --> <property name="cache.use_second_level_cache">true</property> <!-- 在查詢中開啟二級快取 --> <property name="cache.use_query_cache">true</property> <mapping assembly="Model" /> <!-- 配置對映的二級快取 --> <class-cache class="Model.PersonModel,Model" usage="read-write"/> </session-factory> </hibernate-configuration> </configuration>
對映檔案Person.hbm.xml
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.PersonModel,Model" table="Person"> <!-- 配置快取策略 --> <cache usage="read-write"/> <id name="Id" column="Id" type="Int32"> <generator class="native"/> </id> <property name="Name" column="Name" type="String"/> </class> </hibernate-mapping>
Program.cs
class Program { static void Main(string[] args) { ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory(); ISession session = sessionFactory.OpenSession(); IList<PersonModel> ListPerson1 = session.QueryOver<PersonModel>().Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllCategories").List(); Console.WriteLine(ListPerson1[0].Name); IList<PersonModel> ListPerson2 = session.QueryOver<PersonModel>().Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPerson").List(); Console.WriteLine(ListPerson1[0].Name); Console.ReadKey(); } }
輸出結果如下:
我們來梳理下NHibernate的執行過程:
首先,第一次查詢,NHibernate查詢了一級快取,二級快取都沒有需要的資料,因此執行SQL語句,從資料庫獲得資料,返回所需物件。
然後,第二次查詢,NHibernate查詢了一級快取,發現沒有資料,然後查詢二級快取,發現有資料,因此直接返回所需物件。
快取查詢
在NHibernate除了快取持久化類和集合之外,查詢得到的結果集也是可以快取的。如果程式中經常使用同樣的查詢資料,則可以使用查詢快取。
第一步,在配置檔案中,啟用查詢快取:
<property name="cache.use_query_cache">true</property>
啟動了查詢快取之後,NHiberate將建立兩個快取區域。一個用於儲存查詢結果集,有NHibernate.Cache.StandardQueryCache實現。一個用來儲存最近更新的查詢表的時間戳,由NHibernate.Cache.UpdateTimeStampsCache實現。
查詢快取中的集合會根據資料庫的更改而隨之改變。因此對大多數查詢來說,查詢快取的益處不是很大,NHibernate在預設情況下不對查詢進行快取。
如果需要對查詢快取,還要顯式的使用IQuery.SetCacheable(true)方法。IQuery呼叫這個方法後,NHibernate將根據查詢語句、查詢引數、結果集其實範圍等資訊組成一個IQueryKey。接著根據這個IQueryKey到查詢快取中查詢相應資料,查詢成功則直接返回查詢結果。但沒有結果時才去資料查詢,並放入查詢快取。如果IQueryKey資料發生改變,這些IQueryKey及其物件的結果集將從快取中刪除。
Program.cs
class Program { static void Main(string[] args) { ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory(); using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("第一次查詢---------------"); IList<PersonModel> ListPerson = session.QueryOver<PersonModel>().Where(p => p.Id > 1).Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPerson").List(); foreach (var p in ListPerson) { Console.WriteLine(p.Name); } } using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("第一次查詢---------------"); IList<PersonModel> ListPerson = session.QueryOver<PersonModel>().Where(p => p.Id > 1).Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPerson").List(); foreach (var p in ListPerson) { Console.WriteLine(p.Name); } } Console.ReadKey(); }
輸出結果如下:
我們看到,第二次查詢,並沒有執行SQL語句,而是直接從快取中返回資料。
下面,我們來修改一下配置檔案:
<property name="cache.use_query_cache">false</property>
再執行,看到結果如下:
當我們關閉了,查詢快取之後,第二次查詢的時候,也執行SQL語句,從資料庫讀取資料。
快取區域
前面我們已經說過,當快取區域不相同時,查詢也不會使用二級快取,而是會查詢資料庫。
我們將第二條查詢語句的快取區域改成與第一條不同的(查詢快取是開啟的):
IList<PersonModel> ListPerson = session.QueryOver<PersonModel>().Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPersonChange").List();
執行結果如下:
我們看到,更改了快取區域之後,第二次查詢也不會使用二級快取,而是執行了SQL語句,從資料庫獲得返回資料。
命名查詢
可以在對映檔案中定義命名查詢,<query>提供了很多屬性可以用於快取結果。
對映檔案Person.hbm.cs
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Model.PersonModel,Model" table="Person"> <!-- 配置快取策略 --> <cache usage="read-write"/> <id name="Id" column="Id" type="Int32"> <generator class="native"/> </id> <property name="Name" column="Name" type="String"/> </class> <query cacheable="true" cache-mode="normal" name="GetAllPerson"> from PersonModel </query> </hibernate-mapping>
Program.cs
class Program { static void Main(string[] args) { ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory(); using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("--->第一次使用命名查詢"); IList<PersonModel> PersonList1 = session.GetNamedQuery("GetAllPerson").List<PersonModel>(); Console.WriteLine(PersonList1[0].Name); } using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("--->第二次使用命名查詢"); IList<PersonModel> PersonList2 = session.GetNamedQuery("GetAllPerson").List<PersonModel>(); Console.WriteLine(PersonList2[0].Name); } Console.ReadKey(); } }
輸出結果如下:
三、二級快取的管理
NHibernate二級快取由ISessionFactory建立並由ISessionFactory自行維護。我們使用NHibernate操作資料時,ISessionFactory能夠自動同步快取,保證快取的有效性。但我們批量操作資料時,NHibernate往往不能維護快取持久有效。ISessionFactory提供了一系列方法供我們管理二級快取。
移除二級快取的一系方法
- Evict(persistentClass):從二級快取中刪除persistentClass類所有例項
- Evict(persistentClass, id):從二級快取中刪除指定的持久化例項
- EvictEntity(entityName):從二級快取中刪除命名例項
- EvictCollection(roleName):從二級快取中刪除集合
- EvictCollection(roleName, id):從二級快取中刪除指定的集合
- EvictQueries():從二級快取中清空全部查詢結果集
- EvictQueries(cacheRegion):從二級快取中清空指定查詢結果集
下面我們從一個本來會使用快取的例子中,刪除快取空間。
class Program { static void Main(string[] args) { ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory(); using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("第一次查詢---------------"); IList<PersonModel> ListPerson = session.QueryOver<PersonModel>().Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPerson").List(); foreach (var p in ListPerson) { Console.WriteLine(p.Name); } } //刪除快取空間 sessionFactory.EvictQueries("AllPerson"); using (ISession session = sessionFactory.OpenSession()) { Console.WriteLine("第二次查詢---------------"); IList<PersonModel> ListPerson = session.QueryOver<PersonModel>().Cacheable().CacheMode(CacheMode.Normal).CacheRegion("AllPerson").List(); foreach (var p in ListPerson) { Console.WriteLine(p.Name); } } Console.ReadKey(); } }
輸出結果如下:
我們看到,當我們清空了快取空間之後,NHibernate不得不重新從資料庫讀取資料。