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