欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

缓存

程序员文章站 2022-06-18 23:19:55
...

什么是缓存?

缓存是内存上的一块存储空间,存放经常使用的数据,这块空间的查找效率非常高。
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" />

缓存策略的几种形式

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

  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文件中打开查询缓存 ,如
    <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可以直接使用二级缓存