`
JAVA天地
  • 浏览: 674382 次
  • 性别: Icon_minigender_1
  • 来自: 太原
文章分类
社区版块
存档分类
最新评论

hibernate二级缓存攻略 转载:javaeye

阅读更多
很多人对二级缓存都不太了解,或者是有错误的认识,我一直想写一篇文章介绍一下hibernate的二级缓存的,今天终于忍不住了。
我的经验主要来自hibernate2.1版本,基本原理和3.0、3.1是一样的,请原谅我的顽固不化。

hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了。

二级缓存是SessionFactory级别的全局缓存,它底下可以使用不同的缓存类库,比如ehcache、oscache等,需要设置hibernate.cache.provider_class,我们这里用ehcache,在2.1中就是
hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider
如果使用查询缓存,加上
hibernate.cache.use_query_cache=true


缓存可以简单的看成一个Map,通过key在缓存里面找value。

Class的缓存
对于一条记录,也就是一个PO来说,是根据ID来找的,缓存的key就是ID,value是POJO。无论list,load还是iterate,只要读出一个对象,都会填充缓存。但是list不会使用缓存,而iterate会先取数据库selectid出来,然后一个id一个id的load,如果在缓存里面有,就从缓存取,没有的话就去数据库load。假设是读写缓存,需要设置:
<cacheusage="read-write"/>
如果你使用的二级缓存实现是ehcache的话,需要配置ehcache.xml
<cachename="com.xxx.pojo.Foo"maxElementsInMemory="500"eternal="false"timeToLiveSeconds="7200"timeToIdleSeconds="3600"overflowToDisk="true"/>
其中eternal表示缓存是不是永远不超时,timeToLiveSeconds是缓存中每个元素(这里也就是一个POJO)的超时时间,如果eternal="false",超过指定的时间,这个元素就被移走了。timeToIdleSeconds是发呆时间,是可选的。当往缓存里面put的元素超过500个时,如果overflowToDisk="true",就会把缓存中的部分数据保存在硬盘上的临时文件里面。
每个需要缓存的class都要这样配置。如果你没有配置,hibernate会在启动的时候警告你,然后使用defaultCache的配置,这样多个class会共享一个配置。
当某个ID通过hibernate修改时,hibernate会知道,于是移除缓存。
这样大家可能会想,同样的查询条件,第一次先list,第二次再iterate,就可以使用到缓存了。实际上这是很难的,因为你无法判断什么时候是第一次,而且每次查询的条件通常是不一样的,假如数据库里面有100条记录,id从1到100,第一次list的时候出了前50个id,第二次iterate的时候却查询到30至70号id,那么30-50是从缓存里面取的,51到70是从数据库取的,共发送1+20条sql。所以我一直认为iterate没有什么用,总是会有1+N的问题。
(题外话:有说法说大型查询用list会把整个结果集装入内存,很慢,而iterate只selectid比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页20条的话,iterate共需要执行21条语句,list虽然选择若干字段,比iterate第一条selectid语句慢一些,但只有一条语句,不装入整个结果集hibernate还会根据数据库方言做优化,比如使用mysql的limit,整体看来应该还是list快。)
如果想要对list或者iterate查询的结果缓存,就要用到查询缓存了

查询缓存
首先需要配置hibernate.cache.use_query_cache=true
如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了
<cachename="net.sf.hibernate.cache.StandardQueryCache"
maxElementsInMemory="50"eternal="false"timeToIdleSeconds="3600"
timeToLiveSeconds="7200"overflowToDisk="true"/>
<cachename="net.sf.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000"eternal="true"overflowToDisk="true"/>
然后
query.setCacheable(true);//激活查询缓存
query.setCacheRegion("myCacheRegion";//指定要使用的cacheRegion,可选
第二行指定要使用的cacheRegion是myCacheRegion,即你可以给每个查询缓存做一个单独的配置,使用setCacheRegion来做这个指定,需要在ehcache.xml里面配置它:
<cachename="myCacheRegion"maxElementsInMemory="10"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="7200"overflowToDisk="true"/>
如果省略第二行,不设置cacheRegion的话,那么会使用上面提到的标准查询缓存的配置,也就是net.sf.hibernate.cache.StandardQueryCache

对于查询缓存来说,缓存的key是根据hql生成的sql,再加上参数,分页等信息(可以通过日志输出看到,不过它的输出不是很可读,最好改一下它的代码)。
比如hql:
fromCatcwherec.namelike?
生成大致如下的sql:
select*fromcatcwherec.namelike?
参数是"tiger%",那么查询缓存的key*大约*是这样的字符串(我是凭记忆写的,并不精确,不过看了也该明白了):
select*fromcatcwherec.namelike?,parameter:tiger%
这样,保证了同样的查询、同样的参数等条件下具有一样的key。
现在说说缓存的value,如果是list方式的话,value在这里并不是整个结果集,而是查询出来的这一串ID。也就是说,不管是list方法还是iterate方法,第一次查询的时候,它们的查询方式很它们平时的方式是一样的,list执行一条sql,iterate执行1+N条,多出来的行为是它们填充了缓存。但是到同样条件第二次查询的时候,就都和iterate的行为一样了,根据缓存的key去缓存里面查到了value,value是一串id,然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。
可以看出来,查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候,都是既填充查询缓存又填充class缓存的。
这里还有一个很容易被忽视的重要问题,即打开查询缓存以后,即使是list方法也可能遇到1+N的问题!相同条件第一次list的时候,因为查询缓存中找不到,不管class缓存是否存在数据,总是发送一条sql语句到数据库获取全部数据,然后填充查询缓存和class缓存。但是第二次执行的时候,问题就来了,如果你的class缓存的超时时间比较短,现在class缓存都超时了,但是查询缓存还在,那么list方法在获取id串以后,将会一个一个去数据库load!因此,class缓存的超时时间一定不能短于查询缓存设置的超时时间!如果还设置了发呆时间的话,保证class缓存的发呆时间也大于查询的缓存的生存时间。这里还有其他情况,比如class缓存被程序强制evict了,这种情况就请自己注意了。

另外,如果hql查询包含select字句,那么查询缓存里面的value就是整个结果集了。

当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢?
hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。
当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。

Collection缓存
需要在hbm的collection里面设置
<cacheusage="read-write"/>
假如class是Cat,collection叫children,那么ehcache里面配置
<cachename="com.xxx.pojo.Cat.children"
maxElementsInMemory="20"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="7200"
overflowToDisk="true"/>
Collection的缓存和前面查询缓存的list一样,也是只保持一串id,但它不会因为这个表更新过就失效,一个collection缓存仅在这个collection里面的元素有增删时才失效。
这样有一个问题,如果你的collection是根据某个字段排序的,当其中一个元素更新了该字段时,导致顺序改变时,collection缓存里面的顺序没有做更新。

缓存策略
只读缓存(read-only):没有什么好说的
读/写缓存(read-write):程序可能要的更新数据
不严格的读/写缓存(nonstrict-read-write):需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好
事务缓存(transactional):缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境,这个我没有怎么研究过

读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。
在hibernate2.1的ehcache实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到60秒后超时。
不严格读写缓存不锁定缓存中的数据。


使用二级缓存的前置条件
你的hibernate程序对数据库有独占的写访问权,其他的进程更新了数据库,hibernate是不可能知道的。你操作数据库必需直接通过hibernate,如果你调用存储过程,或者自己使用jdbc更新数据库,hibernate也是不知道的。hibernate3.0的大批量更新和删除是不更新二级缓存的,但是据说3.1已经解决了这个问题。
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,但是你却不能自己写jdbc来优化,很郁闷吧。
SessionFactory也提供了移除缓存的方法,你一定要自己写一些JDBC的话,可以调用这些方法移除缓存,这些方法是:
voidevict(ClasspersistentClass)
Evictallentriesfromthesecond-levelcache.
voidevict(ClasspersistentClass,Serializableid)
Evictanentryfromthesecond-levelcache.
voidevictCollection(StringroleName)
Evictallentriesfromthesecond-levelcache.
voidevictCollection(StringroleName,Serializableid)
Evictanentryfromthesecond-levelcache.
voidevictQueries()
Evictanyqueryresultsetscachedinthedefaultquerycacheregion.
voidevictQueries(StringcacheRegion)
Evictanyqueryresultsetscachedinthenamedquerycacheregion.
不过我不建议这样做,因为这样很难维护。比如你现在用JDBC批量更新了某个表,有3个查询缓存会用到这个表,用evictQueries(StringcacheRegion)移除了3个查询缓存,然后用evict(ClasspersistentClass)移除了class缓存,看上去好像完整了。不过哪天你添加了一个相关查询缓存,可能会忘记更新这里的移除代码。如果你的jdbc代码到处都是,在你添加一个查询缓存的时候,还知道其他什么地方也要做相应的改动吗?

----------------------------------------------------

总结:
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。
如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。
分享到:
评论

相关推荐

    spring二级缓存

    `caching`和`Hibernate+ehcache二级缓存配置 - 王贵伟 - JavaEye技术网站.files`、`spring中配置二级缓存.files`这些目录可能包含了相关配置文件和资源。 总的来说,Spring二级缓存通过集成EhCache,实现了跨会话的...

    javaeye Robbin 论缓存技术

    在Javaeye Robbin的讨论中,他提到了缓存技术在多种场景下的应用和重要性。 **缓存的作用** 1. **高速缓冲存储**:缓存是一种位于主存储器和慢速I/O设备之间的高速存储器,其目的是减少对慢速设备的访问次数,提高...

    JavaEye+技术架构

    4. **Hibernate**:另一种可能的ORM(对象关系映射)框架,Hibernate能够自动处理数据库操作,简化了数据库访问,提高了开发效率。 5. **RESTful API**:JavaEye+可能通过RESTful设计原则来构建Web服务接口,以实现...

    Secode_level_cache.zip

    早在2008年开始,我们就借鉴了Java强大的ORM 框架Hibernate的二级对象缓存编写了这个Rails的AR对象缓存插件,并且一直作为JavaEye网站缓存优化的秘密武器来使用,取得了非常理 想的效果。 现在我们将这个插件从...

    Ext+Spring+Hibernate(原创送给javaeye朋友)

    标题 "Ext+Spring+Hibernate(原创送给javaeye朋友)" 暗示了这是一个关于整合Java技术栈的教程或示例项目,其中涉及了三个关键组件:Ext(ExtJS)、Spring和Hibernate。这些技术在企业级Java开发中广泛使用,分别用于...

    hibernate BASEDAO

    8. **缓存支持(Caching)**:如果项目中启用了Hibernate的二级缓存,BaseDAO可能会包含与缓存相关的操作,如`evict(T entity)`来清除特定对象的缓存,或`clearCache()`清空整个缓存。 `Hibernate通用的baseDao - ...

    7.Coherence企业级缓存(六) JMX 管理和监控.pdf

    - [Coherence企业级缓存(二)QuickStart和编程](http://raymondhekk.javaeye.com/blog/260415) - [Coherence企业级缓存(三)四种缓存类型](http://raymondhekk.javaeye.com/blog/260416) - [Coherence企业级缓存(四)...

    Rails缓存架构设计

    ### Rails缓存架构设计 #### 一、高性能Web应用与缓存架构的重要意义 在现代互联网环境下,构建高性能Web应用面临着前所未有的挑战。随着用户数量的激增和技术的发展,Web应用不仅需要处理大规模且高并发的访问...

    JavaEye的Boss分享关于缓存的理解

    【JavaEye的Boss分享关于缓存的理解】 缓存是一种关键的技术,它在现代软件系统中扮演着提升性能和效率的角色。JavaEye的Boss在分享中深入浅出地讲解了缓存的基本概念及其在不同层次的应用。 首先,缓存是高速缓冲...

    javaeye热点阅读

    JavaEye热点阅读是JavaEye论坛推出的2009年2月特辑,旨在为Java学习者和开发者提供最新的知识及行业动态。这份资料包含了多个Java相关的主题,包括但不限于并发编程、开源项目、设计模式、框架应用以及软件开发实践...

    java缓存技术深入剖析

    Javaeye创始人分享的这份“java缓存技术深入剖析”旨在帮助开发者更好地理解和应用缓存机制。以下是对该主题的详细阐述: 一、缓存的基本概念 缓存是一种存储技术,用于临时存储常用数据,以减少对主存储器(如硬盘...

    JavaEye论坛热点推荐_-_2009年09月_-_总第16期.pdf

    - **肤浅理解Hibernate缓存**:简述了Hibernate的一级缓存和二级缓存机制,以及它们在数据持久化中的作用。 - **海量网页存储结构设计**:探讨了如何设计高效的数据结构来存储和检索大量抓取的网页数据。 - **一...

    hibernate的中文问题的解决方案

    **方法二:在`hibernate.cfg.xml`中配置** 1. **修改`hibernate.cfg.xml`文件** - 打开项目的`hibernate.cfg.xml`文件。 - 在`&lt;session-factory&gt;`标签内添加如下属性来指定连接数据库时使用的字符集和是否支持...

    javaeye论坛规则小测验(答案)

    1. **全文转载的禁止**:JavaEye论坛明确规定,技术版块不允许用户全文转载来自其他网站的帖子。这一规定旨在保护原创内容的权益,避免信息的重复和侵权行为。用户在引用他人文章时,应当尊重原作者的劳动成果。 2....

    Struts+spring+hibernate学习笔记! - Struts原理与实践 - JavaEye知识库.files

    Struts、Spring 和 Hibernate 是Java开发中非常著名的三个开源框架,它们在企业级应用开发中起着关键作用。Struts 是一个 MVC(Model-View-Controller)架构的 Web 框架,Spring 提供了一个全面的后端服务管理平台,...

    java hibernate c3p0

    【标题】:“Java Hibernate C3P0”是一个关于Java编程中的持久化框架Hibernate与C3P0连接池的讨论。Hibernate是Java领域广泛使用的对象关系映射(ORM)框架,它简化了数据库操作,使开发者可以使用面向对象的方式来...

Global site tag (gtag.js) - Google Analytics