Hibernate中的缓存以及性能分析



Hibernate 中实现了良好的Cache 机制,可以借助Hibernate内部的Cache提高系统数据读取性能。Hibernate做为一个应用级的数据访问层封装,只能在其作用范围内保持Cache中数据的的有效性,也就是说,在我们的系统与第三方系统共享数据库的情况下,HibernateCache机制可能失效。例如,如果你用access修改了库中的值,那么这就不会更新 JVM中的缓冲池,这就导致了赃数据的产生。

Hibernate在本地JVM 中维护了一个缓冲池,并将从数据库获得的数据保存到池中以供下次重复使用(如果在Hibernate中数据发生了变动,Hibernate同样也会更新池中的数据)。此时,如果有第三方系统对数据库进行了更改,那么,Hibernate并不知道数据库中的数据已经发生了变化,也就是说,池中的数据还是修改之前的版本,下次读取时,Hibernate会将此数据返回给上层代码,从而导致潜在的问题。外部系统的定义,并非限于本系统之外的第三方系统,即使在本系统中,如果出现了绕过Hibernate数据存储机制的其他数据存取手段,那么Cache的有效性也必须细加考量。例如在同一套系统中,基于 Hibernate和基于JDBC的两种数据访问方式并存,那么通过JDBC更新数据库的时候,Hibernate同样无法获知数据更新的情况,从而导致脏数据的出现。

 

基于JavaCache实现,最简单是HashTablehibernate提供了基于HashtableCache 实现机制,不过,由于其性能和功能上的局限,仅供开发调试中使用。同时,Hibernate还提供了面向第三方Cache实现的接口,如JCSEHCacheOSCacheJBoss CacheSwarmCache等。

Hibernate中的Cache大致分为两层,第一层CacheSession实现,属于事务级数据缓冲,一旦事务结束,这个Cache 也就失效。此层Cache 为内置实现,无需我们进行干涉。第二层Cache,是Hibernate 中对其实例范围内的数据进行缓存的管理容器。

12.5.1 二级缓存

Hibernate早期版本中采用了JCSJava Caching System Apache Turbine项目中的一个子项目)作为默认的第二层Cache实现。由于JCS的发展停顿,以及其内在的一些问题(在某些情况下,可能导致内存泄漏以及死锁),新版本的Hibernate已经去除JCS,并用EHCache作为其默认的第二级Cache实现。相对JCSEHCache更加稳定,并具备更好的缓存调度性能,缺陷是目前还无法做到分布式缓存,如果我们的系统需要在多台设备上部署,并共享同一个数据库,必须使用支持分布式缓存的Cache 实现(如JCSJBossCache)以避免出现不同系统实例之间缓存不一致而导致脏数据的情况。HibernateCache进行了良好封装,透明化的Cache机制使得我们在上层结构的实现中无需面对繁琐的Cache维护细节。

HibernateSession在事务级别进行持久化数据的缓存操作。 当然,也有可能分别为每个类(或集合),配置集群、或JVM级别(SessionFactory级别)的缓存。 甚至可以为之插入一个集群的缓存。需要注意的是缓存永远不知道其他应用程序对持久化仓库(数据库)可能进行的修改(即使可以将缓存数据设定为定期失效)。

通过设置hibernate.cache.provider_class属性中指定org.hibernate.cache.CacheProvider的某个实现的类名,可以选择让Hibernate使用哪个缓存实现。Hibernate内置了一些开源缓存实现,提供对它们的内置支持,如表12-3所示。

12-3  Hibernate的内置缓存

缓存

提供类

类型

集群安全否

支持查询缓存

Hashtable

org.hibernate.cache.HashtableCacheProvider

内存

EHCache

org.hibernate.cache.EhCacheProvider

内存,磁盘

OSCache

org.hibernate.cache.OSCacheProvider

内存,磁盘

SwarmCache

org.hibernate.cache.SwarmCacheProvider

集群

JBoss TreeCache

org.hibernate.cache.TreeCacheProvider

集群

12.5.2 缓存映射

配置缓存映射是通过设置类或者集合映射的“<cache>元素”来设定,例如:

<cache

    usage="transactional|read-write|nonstrict-read-write|read-only"   (1)

    region="RegionName"                                              (2)

    include="all|non-lazy"                                             (3)

/>

在上面配置中,其中:

(1) usage(必须)说明了缓存的策略:分别有 transactional read-write nonstrict-read-writeread-only

 (2) region (可选, 默认为类或者集合的名字指定第二级缓存的区域名。

 (3) include (可选,默认为 all) 当属性级延迟抓取打开时, 标记为lazy="true"的实体的属性可能无法被缓存 

 另外可以在hibernate.cfg.xml中指定<class-cache> <collection-cache> 元素。 这里的usage 属性指明了缓存并发策略。

12.5.3 只读缓存策略

如果在应用程序只需读取一个持久化类的实例,而无需对其修改, 那么就可以对其进行只读缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。设置方法如下:

<class name="com.Person" mutable="false">

    <cache usage="read-only"/>

    ....

</class>

12.5.4 /写缓存策略

如果应用程序需要更新数据,那么使用读/写缓存策略比较合适。 如果应用程序要求“序列化事务”的隔离级别,那么就决不能使用这种缓存策略。 如果在JTA环境中使用缓存,那么必须指定hibernate.transaction.manager_lookup_class属性的值, 这样,Hibernate才能知道该应用程序中JTATransactionManager的具体策略。 在其他环境中,必须保证在Session.close()Session.disconnect()调用前, 整个事务已经结束。如果想在集群环境中使用此策略,必须保证底层的缓存实现支持锁定(locking)Hibernate内置的缓存策略并不支持锁定功能。读/写缓存策略的配置方法如下:

<class name="com.Cat" .... >

    <cache usage="read-write"/>

    <set name="kittens" ... >

        <cache usage="read-write"/>

    </set>

</class>

12.5.5 非严格读/写缓存策略

如果应用程序只是偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用非严格读/写缓存策略。如果在JTA环境中使用该策略, 必须为其指定hibernate.transaction.manager_lookup_class属性的值, 在其他环境中,必须保证在Session.close()Session.disconnect()调用前,整个事务已经结束。

12.5.6 事务缓存策略

Hibernate的事务缓存策略提供了全事务的缓存支持, 例如对JBoss TreeCache的支持。这样的缓存只能用于JTA环境中,必须为其指定hibernate.transaction.manager_lookup_class属性。

12.5.7 管理缓存

在应用程序中,当给save()update()saveOrUpdate()方法传递一个对象时,或使用load()get()list()iterate()scroll()方法获得一个对象时该对象都将被加入到Session的内部缓存中。当flush()方法被调用时,对象的状态会和数据库取得同步。 如果不希望此同步操作发生,或者正处理大量对象、需要对有效管理内存时,可以调用evict()方法,从一级缓存中移除这些对象及其集合。例如:

ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //大量数据集

while ( cats.next() ) {

    Cat cat = (Cat) cats.get(0);

    ………

    sess.evict(cat);//移除cat

}

Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。如若要把所有的对象从session缓存中彻底清除,则需要调用Session.clear()方法。

对于二级缓存来说,在SessionFactory中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。 例如:

sessionFactory.evict(Cat.class, catId); //清除某个Cat

sessionFactory.evict(Cat.class);  //清除所有Cats

sessionFactory.evictCollection("Cat.kittens", catId); //清除某个kittens对象的集合

sessionFactory.evictCollection("Cat.kittens"); //清除所有kittens对象的集合

12.5.8 查询缓存

Hibernate中查询的结果集也可以被缓存。只有当经常使用同样的参数进行查询时,这才会有些用处。 要使用查询缓存,需要在配置文件中设置,设置方法如下:

hibernate.cache.use_query_cache true

该设置将会创建两个缓存区域,一个用于保存查询结果集

(org.hibernate.cache.StandardQueryCache) 另一个则用于保存最近查询的一系列表的时间戳(org.hibernate.cache.UpdateTimestampsCache) 需要注意的是在查询缓存中,它并不缓存结果集中所包含的实体的确切状态;它只缓存这些实体的标识符属性的值、以及各值类型的结果。所以查询缓存通常会和二级缓存一起使用。

绝大多数的查询并不能从查询缓存中受益,所以Hibernate默认是不进行查询缓存的。如若需要进行缓存,需要调用 Query.setCacheable(true)方法。这个调用会让查询在执行过程中时先从缓存中查找结果, 并将自己的结果集放到缓存中去。

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

List cats = sess.createQuery("from Cat cat blog where cat.kittens = :kit")

    .setEntity("kittens",kittens)

    .setMaxResults(20)

    .setCacheable(true)

    .setCacheRegion("pages")

    .list();

如果查询需要强行刷新其查询缓存区域,那么应该调用Query.setCacheMode(CacheMode.REFRESH)方法。 CacheMode参数用于控制具体的Session如何与二级缓存进行交互。其主要属性如下:

n         CacheMode.NORMAL - 从二级缓存中读、写数据。

n         CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。

n         CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。

n         CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容。

这对在其他进程中修改底层数据或对那些需要选择性更新特定查询结果集的情况特别有用。这是对SessionFactory.evictQueries()的更为有效的替代方案,同样可以清除查询缓存区域。

相关文章
相关标签/搜索