缓存可以简单的看成一个 Map ,通过 key 在缓存里面找 value 。
一、缓存简介
Cache In Hibernate
HIBERNATE
中的
CACHE
有两级
.
一级是在 Session 范围内的 CACHE . 即每个 Session 有自己的一个 CACHE, 当前操作的对象都会被保留在 CACHE 中 . 但是 Session 关闭后这个 CACHE 也就没有 . 可见这级 CACHE 的生命期是很短的 . (使用 id 进行关键字存储:缓存的 key 就是 ID , value 是 POJO ) ( 缓存的是实体对象 )
另一级
CACHE
是在
SessionFactory
范围的
,
可以被来自同一个
SessionFactory
的
Session
共享
.
在
HIBERNATE
的文档中称其为
SECOND LEVEL CACHE.
显然后者的优势较明显
,
也比较复合当前的使用环境
.
它可以使用不同的缓存实现,如
EhCache
、
JBossCache
、
OsCache
等
(二级缓存是缓存实体对象的)
还有一个类型的 CACHE 就是 QueryCache . 它的作用就是缓存一个 Query 以及 Query 返回对象的 Identifier 以及对象的类型 . 有了 QueryCache 后就可以高效的使用 SECOND LEVEL CACHE.
hibernate
查询缓存
(hibernate
默认是关闭的
)
查询缓存是针对普通属性结果集的缓存
对实体对象的结果集只缓存
id
查询缓存的生命周期,当前关联的表发生修改,那么查询缓存生命周期结束
查询缓存的配置和使用:
1.
启用查询缓存:在
hibernate
.cfg.xml
中加入:
<property name=”hibernate
.cache.use_query_cache”>true</property>
2.
在程序中必须手动启用查询缓存,如:
query.setCacheable(true);
QueryCache 用来缓存查询语句 , 及查询结果集中对象的 Identifier 与 Type. 当再次使用已缓存的 Query 时 , 就可以通过对象的 Identifier 与 Type 在 SECOND LEVEL CACHE 中查找实际的对象 .
对于查询缓存来说,缓存的 key 是根据 hql 生成的 sql ,再加上参数,分页等信息(可以通过日志输出看到,不过它的输出不是很可读,最好改一下它的代码)。
注:一级缓存也叫 session 级的缓存或事务缓存。 Hibernate 二级缓存也称为进程级的缓存或 SessionFactory 级的缓存。二级缓存是全局缓存,它可以被所有的 session 共享。二级缓存的生命周期和 SessionFactory 的生命周期一致, SessionFactory 可以管理二级缓存。
二、缓存的范围
缓存的范围分为
3
类
:
1.
事务范围
事务范围的缓存只能被当前事务访问
,
每个事务都有各自的缓存
,
缓存内的数据通常采用相互关联的对象形式
.
缓存的生命周期依赖于事务的生命周期
,
只有当事务结束时
,
缓存的生命周期才会结束
.
事务范围的缓存使用内存作为存储介质
,
一级缓存就属于事务范围
.
2.
应用范围
应用程序的缓存可以被应用范围内的所有事务共享访问
.
缓存的生命周期依赖于应用的生命周期
,
只有当应用结束时
,
缓存的生命周期才会结束
.
应用范围的缓存可以使用内存或硬盘作为存储介质
,
二级缓存就属于应用范围
.
3.
集群范围
在集群环境中
,
缓存被一个机器或多个机器的进程共享
,
缓存中的数据被复制到集群环境中的每个进程节点
,
进程间通过远程通信来保证缓存中的数据的一致
,
缓存中的数据通常采用对象的松散数据形式
.
三、缓存的方式
有四种,分别为:
CacheConcurrencyStrategy.NONE
CacheConcurrencyStrategy.READ_ONLY ,只读模式,在此模式下,如果对数据进行更新操作,会有异常;
CacheConcurrencyStrategy.READ_WRITE ,读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询;
CacheConcurrencyStrategy.NONSTRICT_READ_WRITE ,不严格的读写模式则不会的缓存数据加锁;
CacheConcurrencyStrategy.TRANSACTIONAL ,事务模式指缓存支持事务,当事务回滚时,缓存也能回滚,只支持 JTA 环境。
缓存的注释写法如下,加在 Entity 的 java 类上:
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
四、缓存的管理
Hibernate
的缓存管理
一级缓存的管理
:
evit(Object obj)
将指定的持久化对象从一级缓存中清除
,
释放对象所占用的内存资源
,
指定对象从持久化状态变为脱管状态
,
从而成为游离对象
.
clear()
将一级缓存中的所有持久化对象清除
,
释放其占用的内存资源
contains(Object obj)
判断指定的对象是否存在于一级缓存中
.
flush()
刷新一级缓存区的内容
,
使之与数据库数据保持同步
.
二级缓存的管理
:
evict(Class arg0, Serializable arg1)
将某个类的指定
ID
的持久化对象从二级缓存中清除
,
释放对象所占用的资源
.
evictCollection(String arg0) 将指定类的所有持久化对象的指定集合从二级缓存中清除 , 释放其占用的内存资源 .
如何避免一次性大量的实体数据入库导致内存溢出
*
先
flush
,再
clear
如果数据量特别大,考虑采用
jdbc
实现,如果
jdbc
也不能满足要求,可以考虑采用数据库本身的特定导入工具
五、什么样的数据不适合放在二级缓存中来
?
下面这几种情况就不适合
加载到二级缓存中
:
1.
经常被修改的数据
2.
绝对不允许出现并发访问的数据
3.
与其他应用共享的数据
下面这己种情况合适
加载到二级缓存中
:
1.
数据更新频率低
2.
允许偶尔出现并发问题的非重要数据
3.
不会被并发访问的数据
4.
常量数据
5.
不会被第三方修改的数据
六、二级缓存的配置
Hibernate
的二级缓存功能是靠配置二级缓存插件来实现的
,Hibernate
为了集成这些插件
,Hibernate
提供了
org.hibernate.cache.CacheProvider
借口
,
它充当缓存插件与
Hibernate
之间的适配器
.
常用的二级缓存插件
EHCache org.hibernate.cache.EhCacheProvider
OSCache org.hibernate.cache.OSCacheProvider
SwarmCahe org.hibernate.cache.SwarmCacheProvider
JBossCache org.hibernate.cache.TreeCacheProvider
设置 hibernate.cache.provider_class 。
我们这里用
ehcache
,如
hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider
由于这是
HIBERNATE
默认的
CACHE
提供者
,
所以无须做什么设置
.
只要在 src 中添加 ehcache 的配置文件 ehcache.xml:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
<cache name="goncha.hb.bean.Person" maxElementsInMemory="10" eternal="false"
timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false" />
<cache name="goncha.hb.bean.Address" maxElementsInMemory="10" eternal="false"
timeToIdleSeconds="100" timeToLiveSeconds="100" overflowToDisk="false" />
</ehcache>
七、哪些方法支持缓存
*get()
*load()
*iterate() (
查询实体对象
)
save()
查询缓存只对 query.list() 起作用
一级缓存测试:
1 . Load 测试 : 在同一个 session 中发出两次 load 查询( 1 )
2 . Get 测试 : 在同一个 session 中发出两次 get 查询( 1 )
3 . iterate 测试 : 在同一个 session 中发出两次 iterator 查询( 1 ( id ) +N , 1 ( id ))
4 . Iterate 查询属性测试 : 同一个 session 中发出两次查询属性( 2 次, iterate 查询普通属性,一级缓存不会缓存,所以会发出 sql )
5 .同一个 session 中先 save ,再发出 load 查询 save 过的数据 --save 是使用缓存的
6 .同一个 session 中先调用 load 查询,然后执行 sessio.clear() 或 session.evict() ,再调用 load 查询( 2 次)
sessio.clear() 或 session.evict() 可以管理一级缓存,一级缓存无法取消,但可以管理 . 上面的语句都会发出 sql 因为一级缓存中的实体被清除了
7 .向数据库中批量加入 1000 条数据
// 每一定条数据就强制 session 将数据持久化,同时清除缓存,避免大量数据造成内存溢出
开启二级缓存测试:
1 .开启两个 session 中发出两次 load 查询( get 与 load 一样, 1 次) ,
2 .开启两个 session ,分别调用 load ,再使用 sessionFactory 清楚二级缓存( 2 次)
3 .一级缓存和二级缓存的交互
session.setCacheMode(CacheMode.GET); // 设置成 只是从二级缓存里读 , 不向二级缓存里写数据 ( 2 )
session.setCacheMode(CacheMode.PUT); // 设置成只是向二级缓存里写数据,不读数据 ( 2 次)
开启 hibernate 查询缓存测试:
1 . 开启查询缓存,关闭二级缓存,开启一个 session ,分别调用 query.list (查询属性)( 1 次)
2 . 开启查询缓存,关闭二级缓存,开启两个 session ,分别调用 query.list (查询属性)
( 1 次)第二次没有去查询数据库,因为查询缓存生命周期与 session 生命周期无关
3 . 开启查询缓存,关闭二级缓存,开启两个 session ,分别调用 query.iterate (查询属性)
( 2 次)第二去查询数据库,因为查询缓存只对 query.list() 起作用,对 query.iterate() 不起作用 , 也就是说 query.iterate() 不使用查询缓存
4 . 关闭查询缓存,关闭二级缓存,开启两个 session ,分别调用 query.list (查询实体对象) 第二去查询数据库,因为 list 默认每次都会发出查询 sql
5 . 开启查询缓存,关闭二级缓存,开启两个 session ,分别调用 query.list (查询实体对象) 第二去查询数据库时,会发出 N 条 sql 语句,因为开启了查询缓存,关闭了二级缓存,那么查询缓存会缓存实体对象的 id ,所以 hibernate 会根据实体对象的 id 去查询相应的实体,如果缓存中不存在相应的实体,那么将发出根据实体 id 查询的 sql 语句,否则不会发出 sql ,使用缓存中的数据
6 . 开启查询缓存,开启二级缓存,开启两个 session ,分别调用 query.list (查询实体对象) 第二不会发出 sql ,因为开启了二级缓存和查询缓存,查询缓存缓存了实体对象的 id 列表, hibernate 会根据实体对象的 id 列表到二级缓存中取得相应的数据
八、二级缓存在项目中的集成和使用
1.
将
echcache.xml
文件拷贝到
src
下
,
二级缓存
hibernate
默认是关闭的,手动开启
2.
开启二级缓存,修改
hibernate
.cfg.xml
文件
,
<property name=”hibernate
.cache.user_second_level_cache”>true</property>
3.
指定缓存产品提供商
<property name=”hibernate
.cache.provider_calss”>org.hibernate
.cache.EhCacheProvider</property>
4.
指定那些实体类使用二级缓存(两种方法,推荐使用第二种)
第一种:在
*.hbm.xml
中,在
<id>
之前加入
<cache usage=”read-only” />,
使用二级缓存
第二种:在
hibernate
.cfg.xml
配置文件中
,
在
<mapping resource=”com/Studnet.hbm.xml” />
后面
加上:
<class-cache class=” com.Studnet” usage=”read-only” />
总结:
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。 hibernate 的二级缓存限制还是比较多的,不方便用 jdbc 可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有 1+N 的问题。不当的使用还可能导致读出脏数据。
如果受不了 hibernate 的诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好 。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。
对于性能的看法是:合理的性能而不是最高的性能。什么是合理的?用户能感觉到(或敏感的)的范围内,不成为物理上业务处理的瓶颈,比如常说的 3 秒之内响应等。
文章出自:http://aumy2008.blogbus.com/logs/41092927.html