Hibernate二级缓存攻略
副标题#e#
许多人对二级缓存都不太相识,可能是有错误的认识,我一直想写一篇文章先容一下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会先取数据库select id出来,然后一个id一个id的load,假如在缓存内里有,就从缓存取,没有的话就去数据库load。假设是读写缓存,需要配置:
<cache usage="read-write"/>
假如你利用的二级缓存实现是ehcache的话,需要设置ehcache.xml
<cache name="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只select id较量好,可是大型查询老是要分页查的,谁也不会真的把整个功效集装进来,如果一页20条的话,iterate共需要执行21条语句,list固然选择若干字段,比iterate第一条select id语句慢一些,但只有一条语句,不装入整个功效集hibernate还会按照数据库方言做优化,好比利用mysql的limit,整体看来应该照旧list快。)
假如想要对list可能iterate查询的功效缓存,就要用到查询缓存了
#p#副标题#e#
查询缓存
首先需要设置hibernate.cache.use_query_cache=true
假如用ehcache,设置ehcache.xml,留意hibernate3.0今后不是net.sf的包名了:
<cache name="net.sf.hibernate.cache.StandardQueryCache"
maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600"
timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="net.sf.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/>
然后
query.setCacheable(true);//激活查询缓存
query.setCacheRegion("myCacheRegion");//指定要利用的cacheRegion,可选
第二行指定要利用的cacheRegion是myCacheRegion,即你可以给每个查询缓存做一个单独的设置,利用setCacheRegion来做这个指定,需要在ehcache.xml内里设置它:
<cache name="myCacheRegion" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true" />
假如省略第二行,不配置cacheRegion的话,那么会利用上面提到的尺度查询缓存的设置,也就是:net.sf.hibernate.cache.StandardQueryCache
对付查询缓存来说,缓存的key是按照hql生成的sql,再加上参数,分页等信息(可以通过日志输出看到,不外它的输出不是很可读,最好改一下它的代码)。
好比hql:
from Cat c where c.name like ?
生成大抵如下的sql:
select * from cat c where c.name like ?
参数是"tiger%",那么查询缓存的key*约莫*是这样的字符串(我是凭影象写的,并不准确,不外看了也该大白了):
select * from cat c where c.name like ? , parameter:tiger%
这样,担保了同样的查询、同样的参数等条件下具有一样的key。
#p#分页标题#e#
此刻说说缓存的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内里配置:
<cache usage="read-write"/>
如果class是Cat,collection叫children,那么ehcache内里设置
<cache name="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秒后超时。
不严格读写缓存不锁定缓存中的数据。 利用二级缓存的前置条件。
#p#分页标题#e#
你的hibernate措施对数据库有独有的写会见权,其他的历程更新了数据库,hibernate是不行能知道的。你操纵数据库必须直接通过hibernate,假如你挪用存储进程,可能本身利用jdbc更新数据库,hibernate也是不知道的。hibernate3.0的大批量更新和删除是不更新二级缓存的,可是听说3.1已包办理了这个问题。
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,可是你却不能本身写jdbc来优化,很郁闷吧。
SessionFactory也提供了移除缓存的要领,你必然要本身写一些JDBC的话,可以挪用这些要领移除缓存,这些要领是:
void evict(Class persistentClass)
Evict all entries from the second-level cache.
void evict(Class persistentClass, Serializable id)
Evict an entry from the second-level cache.
void evictCollection(String roleName)
Evict all entries from the second-level cache.
void evictCollection(String roleName, Serializable id)
Evict an entry from the second-level cache.
void evictQueries()
Evict any query result sets cached in the default query cache region.
void evictQueries(String cacheRegion)
Evict any query result sets cached in the named query cache region.
不外我不发起这样做,因为这样很难维护。好比你此刻用JDBC批量更新了某个表,有3个查询缓存会用到这个表,用evictQueries(String cacheRegion)移除了3个查询缓存,然后用evict(Class persistentClass)移除了class缓存,看上去仿佛完整了。不外哪天你添加了一个相关查询缓存,大概会健忘更新这里的移除代码。假如你的jdbc代码处处都是,在你添加一个查询缓存的时候,还知道其他什么处所也要做相应的窜改吗?
总结:
不要想虽然的觉得缓存必然能提高机能,仅仅在你可以或许驾御它而且条件符合的环境下才是这样的。hibernate的二级缓存限制照旧较量多的,不利便用jdbc大概会大大的低落更新机能。在不相识道理的环境下乱用,大概会有1+N的问题。不妥的利用还大概导致读出脏数据。
假如受不了hibernate的诸多限制,那么照旧本身在应用措施的层面上做缓存吧。
在越高的层面上做缓存,结果就会越好。就仿佛尽量磁盘有缓存,数据库照旧要实现本身的缓存,尽量数据库有缓存,咱们的应用措施照旧要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的较量通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,结果也要好些吧。