是由 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 的二级缓存也称为进程级的缓存或是 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); 禁止将一级缓存中的数据放到二级缓存之中。
* 缓存的对象 à 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 会发出N 条SQL 语句(这N 条语句是根据ID 进行查询实体),因为前面说过,查询实体对象时查询缓存会缓存实体对象的id ,第二次执行query.list(), 将查询缓存中的id 依次取出,分别到一级缓存和二级缓存中查询相应的实体对象,如果存在就使用缓存中的实体对象,否则根据id 发出查询学生的语句。如果开启二级缓存就不会发出N+1 条语句了 。
* 总结: 查询缓存通常会和二级缓存一起使用