ITEEDU

12.2. 第二层缓存(The Second Level Cache)s

HibernateSession是事务级别的持久化数据缓存。再为每个类或者每个集合配置一个集群或者JVM级别(SessionFactory级别)的缓存也是有可能的。你甚至可以插入一个集群的缓存。要小心,缓存永远不会知道其他进程可能对持久化仓库(数据库)进行的修改(即使他们可能设定为经常对缓存的数据进行失效)。

默认情况下,Hibernate使用EHCache进行JVM级别的缓存。但是,对JCS的支持现在已经被废弃了,未来版本的Hibernate将会去掉它。通过hibernate.cache.provider_class属性,你也可以指定其他缓存,只要其实现了net.sf.hibernate.cache.CacheProvider接口。

Table 12.1. Cache Providers

Cache Provider class Type Cluster Safe Query Cache Supported
Hashtable (not intended for production use) net.sf.hibernate.cache.HashtableCacheProvider memory   yes
EHCache net.sf.ehcache.hibernate.Provider memory, disk   yes
OSCache net.sf.hibernate.cache.OSCacheProvider memory, disk   yes
SwarmCache net.sf.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)  
JBoss TreeCache net.sf.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication)  

12.2.1. 映射(Mapping)

类或者集合映射的<cache>元素可能有下列形式:

<cache                                                      (1)
                usage="transactional|read-write|nonstrict-read-write|read-only" />
1

usage 指定了缓存策略: transactional, read-write, nonstrict-read-write 或者 read-only

另外 (推荐首选?), 你可以在hibernate.cfg.xml中指定<class-cache> 和 <collection-cache> 元素。

usage属性指明了缓存并发策略(cache concurrency strategy)

12.2.2. 只读缓存

如果你的应用程序需要读取一个持久化类的实例,但是并不打算修改它们,可以使用read-only 缓存。这是最简单,也是实用性最好的策略。甚至在集群中,它也能完美地运作。

<class name="eg.Immutable" mutable="false">
    ....
    <jcs-cache usage="read-only"/>
</class>

12.2.3. 读/写缓存

如果应用程序需要更新数据,可能read-write缓存比较合适。如果需要可序列化事务隔离级别(serializable transaction isolation level),这种缓存决不能使用。如果在JTA环境中使用这种缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值,给出得到JTA TransactionManager的策略。在其它环境中,你必须确保在Session.close()或者Session.disconnect()调用前,事务已经结束了。 如果你要在集群环境下使用这一策略,你必须确保底层的缓存实现支持锁定(locking)。内置的缓存提供器并不支持。

<class name="eg.Cat" .... >
    <jcs-cache usage="read-write"/>
    ....
    <set name="kittens" ... >
        <jcs-cache usage="read-write"/>
        ....
    </set>
</class>

12.2.4. 不严格的读/写缓存

如果程序偶尔需要更新数据(也就是说,出现两个事务同时更新同一个条目的现象很不常见),也不需要十分严格的事务隔离,可能适用nonstrict-read-write缓存。如果在JTA环境中使用这种缓存,你必须指定hibernate.transaction.manager_lookup_class属性的值,给出得到JTA TransactionManager的策略。在其它环境中,你必须确保在Session.close()或者Session.disconnect()调用前,事务已经结束了。

12.2.5. 事务缓存(transactional)

transactional缓存策略提供了对全事务缓存提供,比如JBoss TreeCache的支持。这样的缓存只能用于JTA环境,你必须指定hibernate.transaction.manager_lookup_class。

没有一种缓存提供器能够支持所有的缓存并发策略。下面的表列出每种提供器与各种并发策略的兼容性。

Table 12.2. 缓存并发策略支持(Cache Concurrency Strategy Support)

Cache read-only nonstrict-read-write read-write transactional
Hashtable (not intended for production use) yes yes yes  
EHCache yes yes yes  
OSCache yes yes yes  
SwarmCache yes yes    
JBoss TreeCache yes     yes

12.3. 管理Session缓存

不管何时你传递一个对象给save(), update()或者 saveOrUpdate() ,或者不管何时你使用load(), find(), iterate()或者filter()取得一个对象的时候,该对象被加入到Session的内部缓存中。当后继的flush()被调用时,对象的状态会和数据库进行同步。如果你在处理大量对象并且需要有效的管理内存的时候,你可能不希望发生这种同步,evict()方法可以从缓存中去掉对象和它的集合。

Iterator cats = sess.iterate("from eg.Cat as cat"); //a huge result set
while ( cats.hasNext() ) {
    Cat cat = (Cat) iter.next();
    doSomethingWithACat(cat);
    sess.evict(cat);
}

Session也提供了一个contains()方法来判断是否一个实例处于这个session的缓存中。

要把所有的对象从session缓存中完全清除,请调用Session.clear()。

For the JVM-level JCS cache, there are methods defined on SessionFactory for evicting the cached state of an instance, entire class, collection instance or entire collection role.

对于第二层缓存来说,在SessionFactory中定义了一些方法来从缓存中清除一个实例、整个类、集合实例或者整个集合。

12.4. 查询缓存(Query Cache)

查询结果集也可以被缓存。只有当经常使用同样的参数进行查询时,这才会有些用处。要使用查询缓存,首先你要打开它,设置hibernate.cache.use_query_cache=true这个属性。这样会创建两个缓存区域——一个保存查询结果集(net.sf.hibernate.cache.QueryCache),另一个保存最近查询的表的时间戳(net.sf.hibernate.cache.UpdateTimestampsCache)。请注意查询缓存并不缓存结果集中包含实体的状态;它只缓存标识符属性的值和值类型的结果。所以查询缓存通常会和第二层缓存一起使用。

大多数查询并不会从缓存中获得什么好处,所以默认查询是不进行缓存的。要进行缓存,调用 Query.setCacheable(true)。这个调用会让查询在执行时去从缓存中查找结果,或者把结果集放到缓存去。

如果你要对查询缓存的失效政策进行精确的控制,你必须调用Query.setCacheRegion()来为每个查询指定一个命名的缓存区域。

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger order by blog.datetime desc")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();