Hibernate的缓存策略

Hibernate 的一级缓存

是由 Session 提供的,因此它只存在于 Session 的生命周期中,当程序调用 save(),update(),saveOrUpdate() 等方法,及调用查询接口 list,filter,iterate 时,如 Session 缓存中不存在相应的对象, Hibernate 会把该对象加入到一级缓存中,当 Session 关闭时,该 Session 所管理的一级缓存也会立即被清除。

注意 :Hibernate 的一级缓存是 Session 所内置的,不能被卸载,也不能进行任何配置

一级缓存采用的是 key-value Map 方式来实现的,在缓存实体对象时,对象的主关键字 ID Map key ,实体对象就是对应的 value 。所以说,一级缓存是以实体对象为单位进行存储的,在访问时使用的是关键字 ID 。虽然, Hibernate 对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过 Session 中提供的方法来对一级缓存的管理进行手工干预。

get 方法获得持久化对象时,首先查找 Session 缓存 ( 一级缓存 ) 是否有该对象,如果有,则获得该对象;如果没有,就会访问数据库,如果数据库中找不到数据,则返回 null
load
方法也是获得数据,但不同的地方是 load 方法已经假定数据库中一定存在该数据,如果在数据库中找不到该数据,则会抛出一个 org.hibernate.ObjectNotFoundException 异常。
load
方法获得对象的过程是: load 方法首先在 Session 缓存中查找对象,如果找不到则查找 SessionFactory 缓存 ( 二级缓存 ), 如果再找不到则访问数据库。值得注意的是, load 方法是假定数据库中一定有该数据,所以使用代理来延迟加载对象,只有在程序中使用了该对象的属性 ( 非主键属性 ) 时, Hibernate 才会进入 load 方法的获得对象过程。所以说,如果数据库中不存在该记录,异常是在程序访问该对象属性时抛出的,而不是在创建这个对象时就抛出。

Hibernate 中迭代( Iterate )查询实体对象 实现 à 1 、查询出我们需要的数据的所有 ID 1 条语句)。 2 、根据每个 ID 查询出每个对象( N 条语句)。

两次迭代( Iterate )查询实体对象 à 第二次迭代会发出查询 ID 的语句,然后 Hibernate 会查看缓存中是否有这些 ID ,如果有就不会再发 SQL 语句。

Hibernate 中迭代( Iterate )查询普通属性 实现 à 直接根据 ID 满足的条件,查询出所有的对象的属性(即属性集合)。

两次迭代( Iterate )查询普通属性 à 两次迭代都会根据 ID 满足的条件,查询出所有的对象的属性(即属性集合)。即不会缓存。

总结: Hibernate 的一级缓存只会对实体对像的查询进行缓存,不会对对象的普通属性进行缓存。

大批量的数据添加 à Hibernate 中添加很大量的数据是,默认会把所有添加的对象都缓存起来,这样有可能导致缓存的溢出,处理这种问题时可以确定多少条记录后显示的调用 flush ,并清理缓存。甚至直接使用 JDBC ,如果仍不能满足需求,可以使用数据库的工具,比如: Oracle SQL Loader

通过 Hibernate 的实现:

 

for(int i=0; i<10000000;i++){
    Student student=new Student();
    student.setName(“qq”+i);
    session.save(student);
    // 每 100 条更新一次
    If(i%100==0){
        session.flush()
        // 清理缓存
        session.clear();
    }
}
session.getTransaction().commit(); 
 

Hibernate 的二级缓存

Hibernate 的二级缓存也称为进程级的缓存或是 sessionFactory 级的缓存,二级缓存可以被所有的 session 共享。二级缓存的生命周期和 sessionFactory 的生命周期是一样的,并且可以通过 sessionFactory 管理二级缓存。二级缓存是缓存实体对象的。缓存可以简单的看成一个 Map ,通过 key 在缓存里面找 value 无论 list load 还是 iterate ,只要读出一个对象,都会填充缓存。但是 list 不会使用缓存 ,而 iterate 会先取数据库 select id 出来,然后一个 id 一个 id load ,如果在缓存里面有,就从缓存取,没有的话就去数据库 load 。有说法说大型查询用 list 会把整个结果集装入内存,很慢,而 iterate select id 比较好,但是大型查询总是要分页查的,谁也不会真的把整个结果集装进来,假如一页 20 条的话, iterate 共需要执行 21 条语句, list 虽然选择若干字段,比 iterate 第一条 select id 语句慢一些,但只有一条语句,不装入整个结果集 hibernate 还会根据数据库方言做优化,比如使用 mysql limit ,整体看来应该还是 list 快。

二级缓存的实现 (EHCache) à 1 、在 Hibernate 包中的 etc 文件夹下拷贝 ehcache.xml src 下。这个文件的配置可以参考文件中的注释。 2 、在 hibernate.cfg.xml 文件中加入缓存的提供商

<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>3 、启动二级缓存,

<property name="hibernate.cache.use_second_level_cache">true</property>4 、指定哪些实体需要使用二级缓存 à 可以在映射文件中采用 <cache> 标签指定(这个标签必须在 ID 标签之前)或者这 hibernate.cfg.xml 文件中统一指定

<class-cache class="com.bjpowernode.hibernate.Student" usage="read-only"/>

缓存使用的策略,通常采用 read-only (缓存在内存中是只读的不会改变)和 read-write (可读可写的)

二级缓存涉及到的一些情况:

* 开启二级缓存,在两个 session 中发 load (或 get )语句,第一个事务提交时肯定会发一条 SQL 语句,但是第二个 session 中不会发出 SQL 语句,因为不同的 session 会共享二级缓存中的数据。

*sessionFactory 管理二级缓存:

sessionFactory.evict(Cat.class, catId); // 清除指定ID 的实体
sessionFactory.evict(Cat.class);  // 清除所有实体
sessionFactory.evictCollection("Cat.kittens", catId); // 清除一个特定实// 体的集合
sessionFactory.evictCollection("Cat.kittens"); // 清除所有实体的集合 

* 一级缓存和二级缓存的交互 à

可以通过 session.setCacheMode(CacheMode.IGNORE); 禁止将一级缓存中的数据放到二级缓存之中。如果另外一个session 进行同样的查询会发出SQL 语句。首先一级缓存失效,二级缓存中有没有放入数据,因此会发第二条语句。

例如:如果没有开启二级缓存,进行大批量更新时,通过没更新固定次数后显示调用flush() 和清除一级缓存,可以防止缓存溢出,但是开了二级缓存后,虽然通过以上步骤清除了一级缓存中的数据,但二级缓存中也放了一份数据,这样是无法避免内存溢出的。所以要通过 session.setCacheMode(CacheMode.IGNORE); 禁止将一级缓存中的数据放到二级缓存之中。

Hibernate 的查询缓存

* 缓存的对象 à 1 、普通属性的结果集2 、查询实体对象时会缓存实体对象的ID

* 缓存的声明周期 à 当管理的表发生修改时,查询缓存的生命周期结束。

* 查询缓存的配置 à hibernate.cfg.xml 文件中添加

<property name="hibernate.cache.use_query_cache">true</property>

开启查询缓存,默认为false

在程序中手动的启用 à query.setCacheable(true)

* 查询缓存的一些使用情况:1 、开启查询缓存,关闭二级缓存,采用了query.list() 查询普通的属性,无论在一个session 还是不同的session 中进行两次查询查询都只发出一条SQL 语句。查询缓存和session 的生命周期没有关系2 、开启查询缓存,关闭二级缓存,两个session 中发query.iterate() 查询,第二个session 会发出查询语句,query.iterate() 查询普通属性它不会使用查询缓存, 查询缓存只对query.list() 起作用 3 开启查询缓存,关闭二级缓存,采用query.list() 查询实体,两个session 中发query.list() 查询,第二个session 会发出NSQL 语句(这N 条语句是根据ID 进行查询实体),因为前面说过,查询实体对象时查询缓存会缓存实体对象的id ,第二次执行query.list(), 将查询缓存中的id 依次取出,分别到一级缓存和二级缓存中查询相应的实体对象,如果存在就使用缓存中的实体对象,否则根据id 发出查询学生的语句。如果开启二级缓存就不会发出N+1 条语句了

* 总结: 查询缓存通常会和二级缓存一起使用

你可能感兴趣的:(Hibernate)