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不得不重新从数据库读取数据。

 

 

posted on 2012-12-03 10:56  逆心 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/kissdodog/archive/2012/12/03/2799206.html