缓存

什么是缓存?

缓存是内存上的一块存储空间,存放经常使用的数据,这块空间的查找效率非常高。
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文件
    true
  • 指定缓存产品提供类,修改hibernate.cfg.xml文件
    org.hibernate.cache.EhCacheProvider
  • 指定那些实体类使用二级缓存(两种方法)
  • 在映射文件中采用标签
  • 在hibernate.cfg.xml文件中,采用标签
二级缓存缓存策略
指定缓存策略的方法

在hbm文件中加入: <cache usage="read-only"/>
这个缓存策略的配置一定要加上,否则便不会有缓存的作用, list/iterator等操作的结果将都不会缓存。
注意:在hbm的class配置中添加配置,表示的是类缓存,如果把这个配置删除,将只缓存ID,不缓存整个对象。(这个时候对list操作,也可能有n+1查询问题)
在hibernate.cfg.xml文件标签里面嵌套定义这样的标签:

缓存策略的几种形式

缓存有几种形式,可以在映射文件中配置:

  1. read-only(只读,适用于很少变更的静态数据/历史数据)
    这是最简单,也是实用性最好的方法
  2. nonstrict-read-write(不严格读写缓存,如果基本不会发生有两个事务同时修改一个数据的时候,比read-write的性能要好)
  3. read-write(效率一般 ,且支持的缓存产品较少)
    例:hibernate_cache_level_2

3、查询缓存

查询缓存只缓存对象的属性,不缓存对象,如果非要缓存对象,那也只缓存id
只能用list使用,list把属性放到缓存,下次直接到缓存里取
对于list来说,只能通过查询缓存来使用二级缓存。如果使用不当,也会产生n+1问题
关联表发生修改,生命周期结束

对实体对象的结果集只缓存id
查询缓存,只对list 这样的操作会起作用

  1. 查询缓存的生命周期为:
    当前关联的表发生修改,那么查询缓存生命周期结束

  2. 查询缓存的配置和使用:
    (1)查询缓存默认情况下关闭,需要打开。
    可以在hibernate.cfg.xml文件中打开查询缓存 ,如
    true
    (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可以直接使用二级缓存

你可能感兴趣的:(缓存)