本节内容
- 引入
- 介绍NHibernate二级缓存
- NHibernate二级缓存提供程序
- 实现NHibernate二级缓存
- 结语
引入
上一篇我介绍了NHibernate内置的一级缓存即ISession缓存。这篇我们来了解下NHibernate二级缓存即ISessionFactory级别缓存。二级缓存是可扩展的,在NHibernate Contrib上提供了第三方NHibernate二级缓存提供程序。
介绍NHibernate二级缓存
NHibernate二级缓存由ISessionFactory创建,可以被所有的ISession共享。
在NHibernate中,当我们启用NHibernate二级缓存。使用ISession进行数据操作时,NHibernate首先从内置缓存(一级缓存)中查找是否存在需要的数据,如果内置缓存不存在需要的数据,则查询二级缓存,如果二级缓存中存在所需数据,则直接使用缓存中数据,否则从数据库中查询数据并放入缓存中。
NHibernate本身提供了一个基于Hashtable的HashtableCache缓存,但是功能非常有限而且性能比较差,不适合在大型应用程序使用,我们可以使用第三方缓存提供程序作为NHibernate二级缓存实现。
但是,使用缓存的缺点就是如果缓存策略设置不当,NHibernate不知道其它应用程序对数据库的修改及时更新缓存。因此,建议只对系统经常使用、数据量不大且不会被其它应用程序修改的只读数据(或很少被修改的数据)使用缓存。
NHibernate二级缓存提供程序
NHibernate提供了NHibernate.Cache.ICacheProvider接口用来支持第三方缓存提供程序实现。开发缓存提供程序时,需要实现该接口作为NHibernate和缓存实现直接的适配器。NHibernate提供了常见的缓存提供程序的内置适配器,这些适配器都实现了NHibernate.Cache.ICacheProvider接口。
除了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配置文件中显式的启用二级缓存。NHibernate二级缓存可以分别为每一个具体的类和集合配置应用级或分布式缓存。
缓存并发策略
提示一下,在NHibernate官方文档中有介绍,详情请参考NHibernate官方文档。当两个独立的事务同时访问数据库时,可能产生丢失更新、不可重复读等并发问题。同样,当两个并发事务同时访问缓存时,也有可能产生各种并发问题。因此,在缓存级别也需要设置相应的并发访问策略。
NHibernate内置四种并发访问策略:
- read-only:只读缓存。适用于只读数据。可用于群集中。
- read-write:读写缓存。
- nonstrict-read-write:非严格读写缓存。不保证缓存与数据库的一致性。
- transactional:事务缓存。提供可重复读的事务隔离级别。
我们动手实现二级缓存吧~~~
Step1:配置第三方缓存提供程序
我们在NHibernate配置文件中通过cache.provider_class属性显式指定缓存实现,属性值为缓存适配器的具体类名。如果你使用上面的第三方缓存提供程序,还需要配置缓存提供程序本身。这里我设置NHibernate本身提供了一个基于Hashtable的HashtableCache缓存。
<property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property>
Step2:显式启用二级缓存
在NHibernate配置文件中使用cache.use_second_level_cache属性显式启用二级缓存,参数为Bool值,这里启用设置为true。
<property name ="cache.use_second_level_cache">true</property>
Step3:配置第三方缓存提供程序本身
如果你使用第三方缓存提供程序,那么需要对第三方缓存提供程序本身进行配置,需要详细配置第三方缓存提供程序缓存属性:保存时间、过期时间、可以缓存对象数量。这里我就使用NHibernate本身提供的HashtableCache缓存,所以这一步就省略了。
Step4:为每一个持久化类和集合指定相应的缓存策略
方法一:在映射文件中通过<cache>元素配置类和集合的缓存策略,在Class元素或者集合元素中添加<cache>元素进行配置。注意:<cache>元素必须在<id>元素之前。
<cache usage="read-only|read-write|nonstrict-read-write" region="默认类或集合名称"/>
方法二:在NHibernate配置文件hibernate.cfg.xml中通过<class-cache>元素和<collection-cache>元素分别配置类和集合的缓存策略。
我还是建议大家使用NHibernate配置文件定义缓存策略,这样可以避免在各个映射文件配置缓存定义而增大维护难度。
指定类:
<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:声明缓存同步策略,就是上面说明的四种缓存策略。
配置文件和映射文件定义不一样,不知道是不是BUG。
Step5:开始测试
在测试之前,我们先看看上面的步骤我们完成了哪些内容。我贴出具体代码:
代码片段1:NHibernate配置文件:
<?xml version="1.0" encoding="utf-8"?> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" > <session-factory name="NHibernateSample.DAL.Test"> <!-- 配置二级缓存实例文件 作者:李永京(YJingLee's Blog) 出处:http://lyj.cnblogs.com --> <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property> <property name="connection.connection_string"> Data Source=.\SQLEXPRESS;Initial Catalog=NHibernateSample; Integrated Security=True;Pooling=False</property> <property name="adonet.batch_size">10</property> <property name="show_sql">true</property> <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property> <property name="use_outer_join">true</property> <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property> <!--1.配置二级缓存提供程序--> <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider</property> <!--2.显式启用二级缓存--> <property name ="cache.use_second_level_cache">true</property> <!--4.启动查询缓存(注:下一篇内容:http://lyj.cnblogs.com)--> <property name="cache.use_query_cache">true</property> <mapping assembly="DomainModel"/> <!--3.配置映射的二级缓存--> <class-cache class="DomainModel.Entities.Customer,DomainModel" usage="read-write"/> </session-factory> </hibernate-configuration>
代码片段2:Customer.hbm.xml映射文件:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel" namespace="DomainModel"> <!-- 配置二级缓存映射文件 作者:李永京(YJingLee's Blog) 出处:http://lyj.cnblogs.com --> <class name ="DomainModel.Entities.Customer,DomainModel" table="Customer"> <cache usage="read-write"/> <id name="CustomerId" type="Int32" unsaved-value="0"> <generator class ="native"></generator> </id> <version name="Version" type="integer" unsaved-value="0"/> <component name="Name" class="DomainModel.Entities.Name,DomainModel"> <property name="Firstname"/> <property name ="Lastname"/> </component> <set name="Orders" table="`Order`" generic="true" inverse="true"> <cache usage="read-only"/> <key column="Customer" foreign-key="FK_CustomerOrders"/> <one-to-many class="DomainModel.Entities.Order,DomainModel"/> </set> </class> </hibernate-mapping>
Step6:测试代码
在不同Session中获取实体:
[Test] public void SessionFactoryCacheTest() { using (_session) { Console.WriteLine("--Session 1--读取持久化实例--"); Customer customer1 = _session.Get<Customer>(1); Assert.IsNotNull(customer1); } ResetSession(); using (_session) { Console.WriteLine("--Session 2--读取持久化实例--"); Customer customer2 = _session.Get<Customer>(1); Assert.IsNotNull(customer2); } }
分析一下:在第一次查询数据时,由于一级、二级缓存中都不存在需要的数据,这时NHibernate从数据库中查询数据。第二次读取同一数据,NHibernate首先从内置缓存(一级缓存)中查找是否存在所需要数据,由于不是在同一个ISession中,所以内置ISession缓存中不存在所需数据,NHibernate则查询二级缓存,这时由于第一次查询了这条数据,所以在二级缓存中存在所需数据,则直接使用缓存中数据。看看输出结果吧: