1. 程式人生 > >NHibernate二級快取(第十一篇)

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 快取模式, 有如下可選:

    1. Ignore:更新資料時將二級快取失效,其它時間不和二級快取互動
    2. Put:向二級快取寫資料,但不從二級快取讀資料
    3. Get:從二級快取讀資料,僅在資料更新時向二級快取寫資料
    4. Normal:預設方式。從二級快取讀/寫資料
    5. 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不得不重新從資料庫讀取資料。