利用OSCache在Hibernate中缓存持久层对象

 一、关于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.use_second_level_cache设置为false关闭所有的hibernate二级缓存。但此属性对指定<cache>的类缺省为true。

        之后,需要在我们的映射文件中指定各个映射实体的Cache策略。缓冲描述符cache可用于描述映射类和集合属性。

 

<class name=" org.hibernate.sample.TUser" .... >

<cache usage="read-write"/>

....

<set name="addresses" .... >

<cache usage="read-only"/>

....

</set>

</class>

        上例中,Class 节点下的cache 描述符指定了针对类TUser 的缓存并发策略为"read-write",即缓冲中的TUser实例为可读可写,而集合属性addresses 的缓存并发策略为只读。

        cache标签的 usage属性提供缓存对象的事务隔离机制,可选值有以下几种:

 

read-only

 

只读。

 

read-write

 

可读可写。

 

nonstrict-read-write

 

如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据,可以采用本选项,以减少无谓的检查,获得较好的性能。

 

transactional

 

事务性cache。cache 的相关操作也被添加到事务之中,如果由于某种原因导致事务失败,我们可以连同缓冲池中的数据一同回滚到事务开始之前的状态。目前Hibernate 内置的Cache 中,只有JBossCache 支持事务性的Cache实现。

        当然,也可以在hibernate.cfg.xml中设置完VO映射后(在mapping标签之后),使用class-cache和collection-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属性指定缓存的读/写机制,还可以使用region属性。如下:

 

<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>

        绝大多数的查询并不能从查询缓存中受益,所以Hibernate在做了以上设置后仍然默认不进行查询缓存的,只有调用 Query.setCacheable(true)方法进行查询缓存。这个调用会让查询在执行过程中时先从缓存中查找结果, 并将自己的结果集放到缓存中去。

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

        如果查询需要强行刷新其查询缓存区域,那么应该调用Query.setCacheMode(CacheMode.REFRESH)方法。 这对在其他进程中修改底层数据(例如,不通过Hibernate修改数据),或对那些需要选择性更新特定查询结果集的情况特别有用。

四、管理缓存

         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语句一般含有文件名不可包含的字符。

        但这并不影响查询缓存的使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值