Cache的层次

1、查询缓存

首先需要配置hibernate.cache.use_query_cache=true

如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了

 

1 < cache name ="net.sf.hibernate.cache.StandardQueryCache"
2
3 maxElementsInMemory ="50" eternal ="false" timeToIdleSeconds ="3600"
4
5 timeToLiveSeconds ="7200" overflowToDisk ="true" />
6
7 < cache name ="net.sf.hibernate.cache.UpdateTimestampsCache"
8
9 maxElementsInMemory ="5000" eternal ="true" overflowToDisk ="true" />
10
11

然后

query.setCacheable(true);//激活查询缓存

query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可选

第二行指定要使用的cacheRegion是myCacheRegion,即你可以给每个查询缓存做一个单独的配置,

使用setCacheRegion来做这个指定,需要在ehcache.xml里面配置它:

 

1 < cache name ="myCacheRegion" maxElementsInMemory ="10" eternal ="false"
2
3 timeToIdleSeconds ="3600" timeToLiveSeconds ="7200" overflowToDisk ="true" />
4
5


如果省略第二行,不设置cacheRegion的话,那么会使用上面提到的标准查询缓存的配置,也就是

net.sf.hibernate.cache.StandardQueryCache

问题:当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢?

hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面

net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。

当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。

注意:

放入缓存中的key是查询的语句,value是查询之后得到的结果集的id列表。

表面看来这样的方案似乎能解决hql利用缓存的问题,但是需要注意的是,构成key的是:

hql生成的sql、sql的参数、排序、分页信息等。也就是说如果你的hql有小小的差异,

比如第一条hql取1-50条数据,第二条hql取20-60条数据,那么hibernate会认为这是两个

完全不同的key,无法重复利用缓存。因此利用率也不高。

查询缓存必须配合二级缓存一起使用,否则极易出现1+N的情况

如果不设置"查询缓存",那么hibernate只会缓存使用load()方法获得的单个持久化对象,

如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法

获得的数据结果集的话,就需要设置hibernate.cache.use_query_cache true才行,如果需要"查询缓存",需要在使用Query或Criteria()时设置其setCacheable(true);属性。
2、Collection缓存

需要在hbm的collection里面设置

< cache usage ="read-write" />

假如class是Cat,collection叫children,那么ehcache里面配置

1 < cache name ="com.xxx.pojo.Cat.children"
2
3 maxElementsInMemory ="20" eternal ="false" timeToIdleSeconds ="3600"
4
5 timeToLiveSeconds ="7200"
6
7 overflowToDisk ="true" />
8

Collection的缓存和前面查询缓存的list一样,也是只保持一串id,但它不会因为这个表更新过就

失效,一个collection缓存仅在这个collection里面的元素有增删时才失效。

这样有一个问题,如果你的collection是根据某个字段排序的,当其中一个元素更新了该字段时,

导致顺序改变时,collection缓存里面的顺序没有做更新
3、Class缓存(也称二级缓存)

可由Hibernate提供的二级缓存实现,它存在于SessionFactory级别,

缓存结构可以看作是一个hash table,key是数据库记录的id,value是id对应的pojo对象。

当用户根据id查询对象的时候(load、iterator方法),会首先在缓存中查找,

如果没有找到再发起数据库查询。但是如果使用hql发起查询(find, query方法)

则不会利用二级缓存,而是直接从数据库获得数据,但是它会把得到的数据放到二级缓存备用。也就是说,基于hql的查询,对二级缓存是只写不读的。
在hibernate.cfg.xml配置:

 

< property name ="hibernate.cache.provider_class" >

org.hibernate.cache.EhCacheProvider

</ property >

hibernate3支持

Hashtable Cache(org.hibernate.cache.HashtableCacheProvider)

EhCache(org.hibernate.cache.EhCacheProvider)

OSCache(org.hibernate.cache.OSCacheProvider)

SwarmCache(org.hibernate.cache.SwarmCacheProvider)

JBossCache(org.hibernate.cache.TreeCacheProvider)

在相应的POJO配置文件中增加:

<!--

指定缓存同步策略:

read-write 读写型

nonstrict-read-write 非严格读写型

read-only 只读型

transactional 事务型

-->

<cache usage="read-write"/>

Cache read-only nonstrict-read-write read-write transactional
EhCache Yes Yes Yes No
OSCache Yes Yes Yes No
SwarmCache Yes Yes No No
TreeCache Yes No No Yes

对于一条记录,也就是一个PO来说,是根据ID来找的,缓存的key就是ID,value是POJO。无论

list,load还是iterate,只要读出一个对象,都会填充缓存。但是list不会使用缓存,而iterate

会先取数据库select id出来,然后一个id一个id的load,如果在缓存里面有,就从缓存取,没有

的话就去数据库load。

当某个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查询的结

果缓存,就要用到查询缓存了。
注意:

对于one-to-many的关联关系,如果对one应用缓存,则应同时对many应用。

二级缓存的失效机制由hibernate控制,当某条数据被修改之后,hibernate会根据它的id去做

缓存失效操作。基于此机制,如果数据表不是被hibernate独占,那么二级缓存无法得到有效控制

hibernate 3.0在做批量修改、批量更新的时候,是不会同步更新二级缓存的

查询缓存和二级缓存是有关联关系的,他们不是完全独立的两套东西。假如一个查询条件hql_1

,第一次被执行的时候,它会从数据库取得数据,然后把查询条件作为key,把返回数据的所有id

列表作为value(请注意仅仅是id)放到查询缓存中,同时整个结果集放到二级缓存,key是id,

value是pojo对象。当你再次执行hql_1,它会从缓存中得到id列表,然后根据这些列表一个一个的

到二级缓存里面去找pojo对象,如果找不到就向数据库发起查询。也就是说,如果二级缓存配置了

超时时间,就有可能出现查询缓存命中了,获得了id列表,但是class里面相应的pojo已经因为超

时被失效,hibernate就会根据id清单,一个一个的去向数据库查询,有多少个id,就执行多少个

sql。该情况将导致性能下降严重。

查询缓存的失效机制也由hibernate控制,数据进入缓存时会有一个timestamp,它和数据表的

timestamp对应。当hibernate环境内发生save、update等操作时,会更新被操作数据表的

timestamp。用户在获取缓存的时候,一旦命中就会检查它的timestamp是否和数据表的timestamp

匹配,如果不,缓存会被失效。因此查询缓存的失效控制是以数据表为粒度的,只要数据表中任何

一条记录发生一点修改,整个表相关的所有查询缓存就都无效了。因此查询缓存的命中率可能会很

低。
4、对方法的缓存

对于调用频率较高的查询类方法,我们希望缓存方法结果

在spring中可利用拦截器(Interceptor)实现,可能如下面的配置:

 

1 < beans >
2
3 < bean id ="cacheInterceptor"
4
5 class ="org.springframework.aop.interceptor.cache.OSCacheInterceptor" >
6
7 <! - 默认刷新时间(秒)- >
8
9 < property name ="defaultRefreshPeriod" >
10
11 < value > 60 </ value >
12
13 </ property >
14
15 < property name ="identifiers" >
16
17 < props >
18
19 < prop key ="java.util.Map" > toString </ prop >
20
21 </ props >
22
23 </ property >
24
25 </ bean >
26
27 <! - 正则表达式匹配 - >
28
29 < bean id ="searcherAdvisor"
30
31 class ="org.springframework.aop.support.RegexpMethodPointcutAdvisor" >
32
33 < property name ="advice" >
34
35 < ref local ="cacheInterceptor" />
36
37 </ property >
38
39 < property name ="patterns" >
40
41 < list >
42
43 < value > .get. </ value >
44
45 </ list >
46
47 </ property >
48
49 </ bean >
50
51 <! - 自动代理 - >
52
53 < bean id ="proxyCreator1"
54
55 class ="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
56
57 < property name ="beanNames" >
58
59 <!-- 需截获方法的bean列表 -->
60
61 < list >
62
63 < value > commonSelect </ value >
64
65 </ list >
66
67 </ property >
68
69 < property name ="interceptorNames" >
70
71 <!-- 截获器列表 -->
72
73 < list >
74
75 < value > searcherAdvisor </ value >
76
77 </ list >
78
79 </ property >
80
81 </ bean >
82
83 < beans >
84
85

这里通过自动代理创建bean,并指定作用其上的interceptor列表,在commonSelect存在searchAdvisor,

而searchAdvisor对匹配*get*模式的方法调用将通过cacheInterceptor进行处理。

你可能感兴趣的:(AOP,数据结构,bean,Hibernate,cache)