缓存
什么是缓存?
缓存是内存上的一块存储空间,存放经常使用的数据,这块空间的查找效率非常高。
hibernate把外存上的空间也作为缓存(二级缓存)
使用缓存目的:提高查找效率
一些结论
- 一级缓存和二级缓存只针对对象,查询缓存针对属性,由list使用
- 缓存使用不当,iterate会造成n+1问题,发大量的sql语句造成缓存拥塞
- iterator查对象发二条sql语句,查属性发一条
- 一级缓存只缓存对象,不缓存对象的属性
- 不同的session有各自的缓存,不能共享
- hibernate不适合处理批量数据
查找数据的过程:
先到一级缓存里查找,如果没找到才发sql语句到数据库里查找
iterator使用一级缓存:
先发一条语句到数据库里查找id,根据id再到缓存里查找对应的对象,如果缓存里没找到,再到数据库里找,一旦找到,就把记录放到一级缓存
什么是n+1问题?
一级缓存使用不当,发大量sql语句,造成缓存拥塞
如何避免n+1问题
先list,后iterator
1、一级缓存(session)
一级缓存(缓存实体对象)
一级缓存很短和session的生命周期一致,一级缓存也叫session级的缓存
哪些方法支持一级缓存:
- save()
- get()
- load()
- list() 特点:只放不用
- iterate(查询实体对象) 特点:先用后放
如何管理一级缓存:
- session.clear(),session.evict()清除缓存
- session.flush()将数据持久化到数据库
如何避免一次性大量的实体数据入库导致内存溢出
- 先flush,再clear
例:hibernate_cache_level_1
2、二级缓存(sessionFactory)
二级缓存也称进程级的缓存或SessionFactory级的缓存,二级缓存可以被所有的session共享
二级缓存的生命周期和SessionFactory的生命周期一致,SessionFactory可以管理二级缓存
二级缓存的配置和使用:
- 将echcache.xml文件拷贝到src下
- 开启二级缓存,修改hibernate.cfg.xml文件
<property name="hibernate.cache.use_second_level_cache">true</property>
- 指定缓存产品提供类,修改hibernate.cfg.xml文件
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
- 指定那些实体类使用二级缓存(两种方法)
- 在映射文件中采用<cache>标签
- 在hibernate.cfg.xml文件中,采用<class-cache>标签
二级缓存缓存策略
指定缓存策略的方法
在hbm文件中加入: <cache usage="read-only"/>
这个缓存策略的配置一定要加上,否则便不会有缓存的作用, list/iterator等操作的结果将都不会缓存。
注意:在hbm的class配置中添加<cache>配置,表示的是类缓存,如果把这个配置删除,将只缓存ID,不缓存整个对象。(这个时候对list操作,也可能有n+1查询问题)
在hibernate.cfg.xml文件<sessionFactory>标签里面嵌套定义这样的标签:<class-cache class="com.bjsxt.hibernate.User2" usage="read-only" />
缓存策略的几种形式
缓存有几种形式,可以在映射文件中配置:
- read-only(只读,适用于很少变更的静态数据/历史数据)
这是最简单,也是实用性最好的方法 - nonstrict-read-write(不严格读写缓存,如果基本不会发生有两个事务同时修改一个数据的时候,比read-write的性能要好)
- read-write(效率一般 ,且支持的缓存产品较少)
例:hibernate_cache_level_2
3、查询缓存
查询缓存只缓存对象的属性,不缓存对象,如果非要缓存对象,那也只缓存id
只能用list使用,list把属性放到缓存,下次直接到缓存里取
对于list来说,只能通过查询缓存来使用二级缓存。如果使用不当,也会产生n+1问题
关联表发生修改,生命周期结束
对实体对象的结果集只缓存id
查询缓存,只对list 这样的操作会起作用
查询缓存的生命周期为:
当前关联的表发生修改,那么查询缓存生命周期结束查询缓存的配置和使用:
(1)查询缓存默认情况下关闭,需要打开。
可以在hibernate.cfg.xml文件中打开查询缓存 ,如<propertyname="hibernate.cache.use_query_cache">true</property>
(2)在程序中必须手动启用查询缓存,如:query.setCacheable(true);
例:hibernate_query_cache
前提:开启查询缓存,开启二级缓存,使用iterator
前提:关闭二级缓存,开启查询缓存
产生n+1问题
过程:首先发sql语句,到数据库里拿到100个对象,把这个100个对象先放到一级缓存,然后开启查询缓存,把这100个对象的id(主属性)放到查询缓存里。接着关闭session,重新打开一个session。第二次发相同的HQL,它会到查询缓存里取得这100个对象的id,通过这100个对象的id到二级缓存找stu对象,但是现在二级缓存是关闭的,它就只能通过这100个id发100条sql语句到数据库里查找记录,这样就产生了n+1问题
如何解决list产生的n+1问题
只需要开启二级缓存
/**
* 开启查询缓存,关闭二级缓存
*
* 开启两个session,分别调用query.list查询实体对象
*/
public void testCache5() {
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Query query = session.createQuery("select s from Student s");
//启用查询查询缓存
query.setCacheable(true);
List students = query.list();
for (Iterator iter=students.iterator();iter.hasNext(); ) {
Student student = (Student)iter.next();
System.out.println(student.getName());
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------");
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Query query = session.createQuery("select s from Student s");
//启用查询查询缓存
query.setCacheable(true);
//会发出n条查询语句,因为开启了查询缓存,关闭了二级缓存,那么查询缓存会缓存实体对象的id
//所以hibernate会根据实体对象的id去查询相应的实体,如果缓存中不存在相应的
//实体那么将发出根据实体id查询的sql语句,否则不会发出sql使用缓存中的数据
List students = query.list();
for (Iterator iter=students.iterator();iter.hasNext(); ) {
Student student = (Student)iter.next();
System.out.println(student.getName());
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
一些结论
- 查询缓存独立于一级缓存,查询缓存的生命周期比一级缓存长
- iterator不使用查询缓存
- list不会使用一级缓存,只能把对象往一级缓存里放
- list可以直接使用查询缓存,存储属性。如果是对象,存放id到查询缓存
- list可以往二级缓存里放数据,但是不能直接取,list必须要通过查询缓存使用二级缓存
- 一旦查询缓存有数据,而关闭了二级缓存,那么这时候使用查询缓存,就会产生n+1问题
- iterator可以直接使用二级缓存
上一篇: golang 文件目录结构_Go项目的文件系统结构
下一篇: sb整合pagehelper