Nhibernate中的缓存分两种,一级缓存和二级缓存。
首先一级缓存,也就是Isession缓存。是nhibernate内置的,生存周期为Isession周期。你第一次查询数据库Isession会保存这次查询的记录,如果你第二次查询这个数据时,Isession会先查看缓存,如果存在这个记录,就直接返回,不存在就查询数据库,并保存缓存再返回。


Customer customer1 = _transaction.GetCustomerById(1);
Console.WriteLine("第二次读取持久化实例");
Customer customer2 = _transaction.GetCustomerById(1);
Assert.AreSame(customer1, customer2);
执行以上语句你会发现第一次查询了数据库,而第二次没有。
就如前面所说,如果你用USING(_SESSION){}的话,把两个操作放在不同session里面,执行的话就会生存两条查询语句。
一级缓存的管理:
一级缓存包含一下几个常用方法:
ISession.Evict(object):从缓存中删除指定实例。
ISession.Clear():清空缓存。
ISession.Contains(object):检查缓存中是否包含指定实例。


Customer customer1 = _transaction.GetCustomerById(1);
Customer customer2 = _transaction.GetCustomerById(2);
//2.加载实例后,缓存包含两个实例
Assert.IsTrue(_session.Contains(customer1));
Assert.IsTrue(_session.Contains(customer2));
//3.从缓存中删除Customer1实例
_session.Evict(customer1);
Assert.IsFalse(_session.Contains(customer1));
Assert.IsTrue(_session.Contains(customer2));
//4.清空ISession缓存,实例不和缓存关联
_session.Clear();
Assert.IsFalse(_session.Contains(customer1));
Assert.IsFalse(_session.Contains(customer2));
以上是一级缓存很简单,接下来是二级。
二级缓存是ISessionFactory级别的缓存,并且而缓存是可扩展的,即可以由第三方提供。
我们这里只用nhibernate内置的测试。
二级缓存默认是不启用的,我们要对其进行配置,完整配置如下:


<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory name="NHibernate.Test">
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">
Data Source=.;Initial Catalog=NhibernateSample;Persist Security Info=True;User ID=sa;Password=sa
</property>
<property name="adonet.batch_size">10</property>
<property name="show_sql">false</property>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="use_outer_join">true</property>
<property name="command_timeout">60</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>
<!--3.启动查询缓存-->
<property name="cache.use_query_cache">true</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<mapping assembly="NHibernateSample.Domain"/>
<!--4.配置映射的二级缓存-->
<class-cache class="NHibernateSample.Domain.Entities.Customer,NHibernateSample.Domain" usage="read-write"/>
</session-factory>
</hibernate-configuration>
这里要注意第四个一定要放在最后,不然会报错。
第一条是:因为有第三方提供的,所以我们要先配置提供方,然后第二条启用,第三天开启查询缓存,第四你的数据库映射文件实体文件。
以上是对全局的配置,我们还要对映射文件进行配置。


<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernateSample.Domain"
namespace="NHibernateSample.Domain.Entities">
<class name ="Customer">
<cache usage="read-write"/>
<id name="Id" column ="CustomerId">
<generator class ="increment"/>
</id>
<version name="Version" column="Version" type="integer" unsaved-value="0"/>
<property name ="FirstName"/>
<property name ="LastName"/>
<set name="Orders" table="[Order]" generic="true" inverse="true">
<cache usage="read-only"/>
<key column="Customer" foreign-key="FK_CustomerOrders"/>
<one-to-many class="NHibernateSample.Domain.Entities.Order,NHibernateSample.Domain"/>
</set>
<sql-insert>exec CustomerInsert ?,?,?,?</sql-insert>
<sql-delete>exec CustomerDelete ?,?</sql-delete>
</class>
<sql-query name="ParamSProcs">
<return-scalar column="value" type="long"/>
<return-scalar column="value2" type="long"/>
exec paramSProcs ?, ?
</sql-query>
<sql-query name="EntitySProcs">
<return class="NHibernateSample.Domain.Entities.Customer,NHibernateSample.Domain"/>
exec entitySProcs :customerId
</sql-query>
</hibernate-mapping>
上面的usage可以配置4种属性值:
read-only:只读缓存。适用于只读数据。可用于群集中。read-write:读写缓存。nonstrict-read-write:非严格读写缓存。不保证缓存与数据库的一致性。transactional:事务缓存。提供可重复读的事务隔离级别。
配置好后还是用刚才的例子测试,你会发现,就算是两个独立的Isession操作,只要是一样的查询,只会查询一次。
这是因为,我们开启了二级缓存,在第一次查询数据时,由于一级、二级缓存中都不存在需要的数据,这时NHibernate从数据库中查询数据。第二次读取同一数据,NHibernate首先从内置缓存(一级缓存)中查找是否存在所需要数据,由于不是在同一个ISession中,所以内置ISession缓存中不存在所需数据,NHibernate则查询二级缓存,这时由于第一次查询了这条数据,所以在二级缓存中存在所需数据,则直接使用缓存中数据。
那如果是更新呢,缓存的数据也会更新吗?
private void ResetSession()
{
if (_session.IsOpen)
_session.Close();
_session = _helper.GetSession();
}


using (_session)
{
using (var tx = _session.BeginTransaction())
{
Console.WriteLine("第一次读取持久化实例");
Customer customer1 = _session.Get<Customer>(1);
Console.WriteLine("更新持久化实例");
customer1.Name.Firstname =firstname;
tx.Commit();
}
}
ResetSession();
Console.WriteLine("第二次读取持久化实例");
using (_session)
{
Customer customer2 = _session.Get<Customer>(1);
Console.WriteLine("新FirstName为:{0}",customer2.Name.Firstname);
Assert.AreEqual(customer2.Name.Firstname, firstname);
}
以上第二次查询的会是什么结果?
缓存更新了,为什么,因为我们设置了读写缓存,因此会自动更新,具体如下:
在第一次查询数据时,由于一级、二级缓存中都不存在需要的数据,这时NHibernate从数据库中查询数据。我们修改这条数据并提交到数据库中,NHibernate执行一条更新语句,由于我们设置了读写缓存策略,NHibernate更新了二级缓存中的数据内容,第二次读取这条数据,NHibernate首先从内置缓存(一级缓存)中查找是否存在所需要数据,由于不是在同一个ISession中,所以内置ISession缓存中不存在所需数据,NHibernate则查询二级缓存,这时由于第一次查询了这条数据,所以在二级缓存中存在所需数据,则直接使用缓存中数据。这时缓存中的数据也是更新的。
以上是缓存持久化类和集合,我们还可以缓存查询结果集,就是我们全局配置里面的<property name="cache.use_query_cache">true</property>
不过有一点需要注意,这种缓存并不是永久有效,只要缓存的表更新了,缓存就消失。
1.使用IQuery.SetCacheable(true)方法缓存查询结果,第二次查询相同条件时,直接从缓存查询中读取。


{
Console.WriteLine("第一次查询某数据,显式缓存查询结果");
IList<Customer> customers =
_session.CreateQuery("from Customer c where c.CustomerId > 2")
.SetCacheable(true)
.List<Customer>();
Assert.AreEqual(11, customers.Count);
}
ResetSession();
using (_session)
{
Console.WriteLine("第二次查询某数据,显式缓存查询结果");
IList<Customer> customers =
_session.CreateQuery("from Customer c where c.CustomerId > 2")
.SetCacheable(true)
.List<Customer>();
Assert.AreEqual(11, customers.Count);
}
结果是:第二次直接使用缓存的。
2.使用.SetCacheRegion("cacheRegion")给查询缓存指定了特定的命名缓存区域,该查询缓存的缓存策略将由二级缓存的命名区域负责:


{
Console.WriteLine("第一次查询某数据,显式缓存查询结果");
IList<Customer> customers =
_session.CreateQuery("from Customer c where c.CustomerId > 2")
.SetCacheable(true)
.SetCacheRegion("queryCache")
.List<Customer>();
Assert.AreEqual(11, customers.Count);
}
ResetSession();
using (_session)
{
Console.WriteLine("第二次查询某数据,显式缓存查询结果");
IList<Customer> customers =
_session.CreateQuery("from Customer c where c.CustomerId > 2")
.SetCacheable(true)
.SetCacheRegion("queryCache")
.List<Customer>();
Assert.AreEqual(11, customers.Count);
}
说明:第一次查询出来的结果集被存储在名为queryCache的缓存区域,第二次同样在这个缓存区域里寻找需要数据,如果第二次没有指定或者指定别的缓存区域则没有需要的数据,就要到数据库中查询了。
3.可以在映射文件中定义命名查询,<query>元素提供了很多属性,可以用于缓存结果:
<query cacheable ="true" cache-mode="normal" name="selectCustomer">from Customer</query>
Customer.hbm.xml映射文件中定义名为selectCustomer的查询由于查询所有Customer并启用缓存查询,缓存模式为默认方式.


{
Console.WriteLine("--->第一次使用命名查询");
IList<Customer> customers = _session.GetNamedQuery("selectCustomer")
.List<Customer>();
}
ResetSession();
using (_session)
{
Console.WriteLine("--->第二次使用命名查询");
IList<Customer> customers = _session.GetNamedQuery("selectCustomer")
.List<Customer>();
}
结果是第二次使用了缓存。
二级缓存丢份儿管理:
NHibernate二级缓存由ISessionFactory创建并由ISessionFactory自行维护。我们使用NHibernate操作数据时,ISessionFactory能够自动同步缓存,保证缓存的有效性。但是当我们批量操作数据时,往往NHibernate不能维护缓存持久有效。ISessionFactory提供了可编程方式的缓存管理方法。
ISessionFactory提供了一系列的EvictXXX()方法可以方便的从二级缓存中删除一个实例、删除一个集合、一个命名缓存等操作
- Evict(persistentClass):从二级缓存中删除persistentClass类所有实例
- Evict(persistentClass, id):从二级缓存中删除指定的持久化实例
- EvictEntity(entityName):从二级缓存中删除命名实例
- EvictCollection(roleName):从二级缓存中删除集合
- EvictCollection(roleName, id):从二级缓存中删除指定的集合
- EvictQueries():从二级缓存中刷新全部查询结果集
- EvictQueries(cacheRegion):从二级缓存中刷新指定查询结果集
ISession内置缓存可以共享ISessionFactory缓存,通过指定ISession的CacheMode可以控制ISession和ISessionFactory的交互方式。ISession可以通过以下五种方式和ISessionFactory交互:
- Ignore:更新数据时将二级缓存失效,其它时间不和二级缓存交互
- Put:向二级缓存写数据,但不从二级缓存读数据
- Get:从二级缓存读数据,仅在数据更新时向二级缓存写数据
- Normal:默认方式。从二级缓存读/写数据
- Refresh:向二级缓存写数据,想不从二级缓存读数据,通过在配置文件设置cache.use_minimal_puts从数据库中读取数据时,强制二级缓存刷新
例子就不写了,和一级差不多的,自己去试一下。
结语:常用的nhibernate方法李永京都写了就这么多,这10篇文章写下来几乎全是抄袭李永京的吧,-_-!。自己也想写点原创的,但个人经验不足,写了又怕误人子弟,因此全部就照搬了,然后把李永京写的不全面的补上一点。当然整片写下了,大多数都是自己亲手测试了的,可以说自己可以配置和简单使用NHibernate了。这10篇文章估计也没多少人看,因为我没有推广到首页发布区或者勾选类别,因为全是抄袭的。。。,但是这篇文章还没完,还有最后一篇,接下来我会学习其他的内容了,设计模式,AOP,权限管理等等,把这些学完,我会自己动手把这些所学组合起来搭配一个框架,把这些抄袭的东西变成自己东西。但是不知道要等多长时间了呵呵,慢慢来吧只要有心去学。
~END~