一、关于Hibernate的二级缓存
Hibernate 中实现了良好的Cache 机制,可以借助Hibernate 内部的Cache迅速提高系统数据读取性能。Hibernate 的Cache机制是透明化的,在上层结构的实现中无需进行繁琐的Cache维护细节。
Hibernate中的Cache大致分为两层。第一层Cache由Session实现,属于事务级(缓存的对象在一个事务范围之内有效)数据缓冲,一旦事务结束,这个Cache 也就失效。此层Cache 为内置实现,无需干涉。事务级缓存是任何一个ORM组件必须的,Hibernate的事务级缓存(一级缓存)由Session的方法(如get、save、update、delete、clear、flush、evict、close)、事务的commit以及清理模式(FlushMode.AUTO、FlushMode.COMMIT、 FlushMode.NEVER)来决定的。具体参见《精通Hibernate:Java对象持久化技术详解》(7.2理解Session的缓存/7.3在Hibernate应用中Java对象的状态)。
第二层Cache由Sessionfaction,是Hibernate 中对其实例范围内的数据进行缓存的管理容器,属于JVM级(缓存的对象在在一个JVM范围之内有效)甚至集群级(缓存的对象在多个JVM范围之内有效)数据缓冲。缓存的数据可以被应用中的所有事务共享,必须采取必要的事务隔离机制。具体参见《精通Hibernate:Java对象持久化技术详解》(13.2Hibernate的二级缓存结构/13.4管理的第二级缓存/13.6小结)。
Hibernate早期版本中采用了JCS(Java Caching System -Apache Turbine项目中的一个子项目)作为默认的第二层Cache实现。由于JCS的发展停顿,以及其内在的一些问题(在某些情况下,可能导致内存泄漏以及死锁),新版本的Hibernate已经将JCS去除,并用EHCache作为其默认的第二级Cache实现,也可进行配置使用其它缓存。
二、设置Hibernate的二级缓存
使用OSCache作为Hibernate的第二层Cache,除了配置oscache.properties外,需要在hibernate.cfg.xml配置以下参数:
<hibernate-configuration> <session-factory> …… <property name="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</property> <property name="cache.use_second_level_cache">true</property> …… </session-factory> </hibernate-configuration> |
之后,需要在我们的映射文件中指定各个映射实体的Cache策略。缓冲描述符cache可用于描述映射类和集合属性。
<class name=" org.hibernate.sample.TUser" .... > <cache usage="read-write"/> .... <set name="addresses" .... > <cache usage="read-only"/> .... </set> </class> |
cache标签的 usage属性提供缓存对象的事务隔离机制,可选值有以下几种:
read-only |
只读。 |
read-write |
可读可写。 |
nonstrict-read-write |
如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据,可以采用本选项,以减少无谓的检查,获得较好的性能。 |
transactional |
事务性cache。cache 的相关操作也被添加到事务之中,如果由于某种原因导致事务失败,我们可以连同缓冲池中的数据一同回滚到事务开始之前的状态。目前Hibernate 内置的Cache 中,只有JBossCache 支持事务性的Cache实现。 |
<hibernate-configuration> <session-factory> …… …… </session-factory> <!—映射文件--> <mapping resource=”com/cayman/usermanage/vo/Group.hbm.xml”/> <mapping resource=”com/cayman/usermanage/vo/User.hbm.xml”/>
<!—缓存策略--> <class-cache class=”com.cayman.usermanage.vo.Group” usage=”read-write”/> <class-cache class=”com.cayman.usermanage.vo.User” usage=”read-write”/> <collection-cache class=”com.cayman.usermanage.vo.Group.users” usage=”read-write”/>
</hibernate-configuration> |
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
region属性指定缓存的区域,默认是类的全限定名。利用缓存区域, 以更精确的指定每个区域的缓存超前策略。如果指定了缓存区域前缀(在hibernate.cfg.xml中设置cache.region_prefix属性为一个字符串),则所有的缓存区域名前将加上这个前缀。
include属性后续再做说明。
三、查询缓存
Hibernate对查询的结果集也可以被缓存。只有当经常使用同样的参数进行查询时,这才会有用。在查询缓存中,它并不缓存结果集中所包含的实体的确切状态;它只缓存这些实体的标识符属性的值、以及各值类型的结果。 所以查询缓存通常会和二级缓存一起使用。
需要在hibernate.cfg.xml配置以下参数:
<hibernate-configuration> <session-factory> <property name="cache.use_query_cache">true</property> …… </session-factory> </hibernate-configuration> |
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();
如果要对查询缓存的失效政策进行精确的控制,必须调用Query.setCacheRegion()方法, 为每个查询指定其命名的缓存区域。 然后在oscache.properties里可以进行如下设置(尚未测试通过):
com.mypackage.domain.Customer.refresh.period = 4000 com.mypackage.domain.Customer.cron = * * 31 Feb * # 如果使用 OSCache为Hibernate提供的OSCacheProvider,则只能设一个 cache.capacity,#不能对缓存区域进行更精确的设置了 com.mypackage.domain.Customer.capacity = 5000 |
四、管理缓存
1、 在SessionFactory中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。 下次从数据库读取时被清除的缓存项再次加入到缓存中。
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
2、通过Session、Query、Criteria的setCacheMode()设置参数,可控制具体的Session如何与二级缓存进行交互。
- CacheMode.NORMAL - 从二级缓存中读、写数据。
- CacheMode.IGNORE - 仅在数据更新时对二级缓存中读、写数据。
- CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
- CacheMode.PUT - 不从二级缓存中读数据,只当缓存从数据库中读数据时向二级缓存写数据。
- CacheMode.REFRESH -不从二级缓存中读数据,只当缓存从数据库中读数据时向二级缓存写数据。在这种模式下, hibernate.cache.use_minimal_puts的设置效果将不起作用,从而强制二级缓存从数据库中读取数据,刷新缓存内容。 hibernate.cache.use_minimal_puts设置为true时指定Session尽量少的写缓存尽量多的读缓存。
五、可能出现的问题
1、缓存的范围
需要注意的是:Hibernate做为一个应用级的数据访问层封装,只能在其作用范围内保持Cache中数据的的有效性,也就是说,在我们的系统与第三方系统共享数据库的情况下,Hibernate的Cache机制可能失效。
Hibernate 在本地JVM 中维护了一个缓冲池,并将从数据库获得的数据保存到池中以供下次重复使用(如果在Hibernate中数据发生了变动,Hibernate同样也会更新池中的数据版本)。
此时,如果有第三方系统对数据库进行了更改,那么,Hibernate并不知道数据库中的数据已经发生了变化,也就是说,池中的数据还是修改之前的版本,下次读取时,Hibernate会将此数据返回给上层代码,从而导致潜在的问题。
外部系统的定义,并非限于本系统之外的第三方系统,即使在本系统中,如果出现了绕过Hibernate数据存储机制的其他数据存取手段,那么Cache的有效性也必须细加考量。如,在同一套系统中,基于Hibernate和基于JDBC的两种数据访问方式并存,那么通过JDBC更新数据库的时候,Hibernate同样无法获知数据更新的情况,从而导致脏数据的出现。
2、集群支持
OSCache的最近版本具有了集群缓存的功能,但从Hibernate2.1以来提供的OSCacheProvider一直没有封装OSCache的集群功能,而且OSCache项目组向Hibernate项目组提供修补意见也还没有被采用。所以,OSCache针对Hibernate2.1和3.0各推荐自己的OSCacheProvider,实现了Hibernate的统一接口org.hibernate.cache.CacheProvider,源代码见《OSCacheProvider.java》。
集群支持的设置分两步。
第一步,把OSCacheProvider.java放在自己的包里(比如包名叫com.cayman.cache),并修改hibernate.cfg.xml如下:
<property name="hibernate.cache.provider_class">com.cayman.cache.OSCacheProvider</property>
在hibernate中,OSCacheProvider.java类引用了OSCache.java类(同在包org.hibernate.cache下)。OSCache项目组也给出了相应的OSCache.java类(同OSCacheProvider.java一样放在自己的包com.cayman.cache下),源代码见《OSCache.java》。
缺省的刷新期由CacheEntry.INDEFINITE_EXPIRY定义(CacheEntry.java在com.opensymphony.oscache.base包下)。
第二步,在oscache.properties里设置相应的集群属性。
使用 OSCache为Hibernate提供的OSCacheProvider,则只能设一个 cache.capacity,不能对缓存区域进行更精确的设置了。
3、查看缓存区域
因为Hibernate自己提供的适配器org.hibernate.cache.OSCache类和OSCache为Hibernate提.的适配器类都没有实现toMap方法,只是抛出UnsupportedOperationException,所以在使用Hibernate的统计查看特定缓存区域中的缓存项时会出错。
查看特定缓存区域中缓存项的如下代码:
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
要解决整个问题,必须自己实现Hibernate提供的适配器类(org.hibernate.cache.OSCache)或OSCache为Hibernate提供的适配器类(OSCache)的toMap方法。
4、HIBERNATE查询缓存
使用Hibernate的查询缓存时,Hibernate把生成的sql语句作为OSCache缓存的key。这是如果要生成硬盘缓存文件往往会出错,因为OSCache使用key作为文件名,而Hibernate把生成的sql语句一般含有文件名不可包含的字符。
但这并不影响查询缓存的使用。