[Nhibernate]二级缓存(一)
目录
写在前面
上篇文章介绍了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过程中难免遇到各种各样的错误,学习的过程也是解决各种异常的过程。
关于二级缓存中简单的查询缓存就介绍到这里,下篇文章将介绍二级缓存针对删除,修改的策略及缓存的管理的内容。
参考文章
-
博客地址:http://www.cnblogs.com/wolf-sun/
博客版权:如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步! 再次感谢您耐心的读完本篇文章。