博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Hibernate-缓存
阅读量:5235 次
发布时间:2019-06-14

本文共 31640 字,大约阅读时间需要 105 分钟。

  缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,目的是为了减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能.

  Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应用程序对物理数据源的访问,使得程序的运行性能明显的提升.同时,为了提升数据正确性,在特定的时刻或事件会同步缓存和物理数据源的数据。

 

  Hibernate缓存分类: 

1.一级缓存

  一级缓存又称为“Session的缓存”。

  Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。

  一级缓存中,持久化类的每个实例都具有唯一的OID。

  hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。

  hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果事务提交或回滚了,我们称session就关闭了,生命周期结束了。

  缓存和连接池的区别:

  缓存和池都是放在内存里,实现是一样的,都是为了提高性能的。但有细微的差别,池是重量级的,里面的数据是一样的,比如一个池里放100个Connection连接对象,这个100个都是一样的。缓存里的数据,每个都不一样。比如读取100条数据库记录放到缓存里,这100条记录都不一样。

 

 

 

 

 

 

2.二级缓存

  SessionFactory的缓存分为内置缓存和外置缓存.内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定义SQL语句等),对于应用程序来说,它是只读的.外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备.

  由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。

    二级缓存需要sessionFactory来管理,它是进初级的缓存,所有人都可以使用,它是共享的。

  二级缓存比较复杂,一般用第三方产品。hibernate提供了一个简单实现,用Hashtable做的,只能作为我们的测试使用,商用还是需要第三方产品。

使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。

 

  第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。

  Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。

  Hibernate的二级缓存功能是靠配置二级缓存插件来实现的

  

EHCache  org.hibernate.cache.EhCacheProvider OSCache  org.hibernate.cache.OSCacheProvider SwarmCahe  org.hibernate.cache.SwarmCacheProvider JBossCache  org.hibernate.cache.TreeCacheProvider

 

 

 

 

 

什么样的数据适合存放到第二级缓存中?   

1) 很少被修改的数据   2) 不是很重要的数据,允许出现偶尔并发的数据   3) 不会被并发访问的数据   4) 常量数据   不适合存放到第二级缓存的数据?   1) 经常被修改的数据   2) 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发   3) 与其他应用共享的数据。

 

 

 

3.Session的延迟加载实现要解决两个问题:正常关闭连接和确保请求中访问的是同一个session。

  Hibernate session就是java.sql.Connection的一层高级封装,一个session对应了一个Connection。

http请求结束后正确的关闭session(过滤器实现了session的正常关闭);延迟加载必须保证是同一个session(session绑定在ThreadLocal)。

 

4.Hibernate查找对象如何应用缓存?

  当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;

  查不到,如果配置了二级缓存,那么从二级缓存中查;

  如果都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增加数据的时候,同时更新缓存。

 

5.一级缓存与二级缓存的对比图。

 

一级缓存

二级缓存

存放数据的形式

相互关联的持久化对象

对象的散装数据

缓存的范围

事务范围,每个事务都拥有单独的一级缓存

进程范围或集群范围,缓存被同一个进程或集群范围内所有事务共享

并发访问策略

由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略

由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别

数据过期策略

处于一级缓存中的对象永远不会过期,除非应用程序显示清空或者清空特定对象

必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间

物理介质

内存

内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中

缓存软件实现

在Hibernate的Session的实现中包含

由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中

启用缓存方式

只要通过Session接口来执行保存,更新,删除,加载,查询,Hibernate就会启用一级缓存,对于批量操作,如不希望启用一级缓存,直接通过JDBCAPI来执行

用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中

用户管理缓存的方式

一级缓存的物理介质为内存,由于内存的容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐

二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择需要使用第二级缓存的持久化类,设置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不推荐

 

 

  缓存范围

Hibernate的一级缓存和二级缓存都位于均位于持久层,且均用于存放数据库数据的副本,最大的区别就是缓存的范围各不一样.

  

  缓存的范围分为3类: 

1.事务范围    事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围. 2.应用范围    应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围. 3.集群范围    在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.

 

 

 

  缓存机制应用

1.  一级缓存的管理:

evit(Object obj)  将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。clear()  将一级缓存中的所有持久化对象清除,释放其占用的内存资源。contains(Object obj) 判断指定的对象是否存在于一级缓存中。flush() 刷新一级缓存区的内容,使之与数据库数据保持同步。

 

 

2.一级缓存应用: save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。 get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。 使用HQL和QBC等从数据库中查询数据。

public class Client{    public static void main(String[] args)    {        Session session = HibernateUtil.getSessionFactory().openSession();        Transaction tx = null;        try        {            /*开启一个事务*/            tx = session.beginTransaction();            /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/            Customer customer1 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002");            System.out.println("customer.getUsername is"+customer1.getUsername());            /*事务提交*/            tx.commit();                        System.out.println("-------------------------------------");                        /*开启一个新事务*/            tx = session.beginTransaction();            /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/            Customer customer2 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002");            System.out.println("customer2.getUsername is"+customer2.getUsername());            /*事务提交*/            tx.commit();                        System.out.println("-------------------------------------");                        /*比较两个get()方法获取的对象是否是同一个对象*/            System.out.println("customer1 == customer2 result is "+(customer1==customer2));        }        catch (Exception e)        {            if(tx!=null)            {                tx.rollback();            }        }        finally        {            session.close();        }    }}

 

结果

结果Hibernate:     select        customer0_.id as id0_0_,        custo mer0_.username as username0_0_,        customer0_.balance as balance0_0_     from        customer customer0_     where        customer0_.id=?customer.getUsername islisi-------------------------------------customer2.getUsername islisi-------------------------------------customer1 == customer2 result is true

 

输出结果中只包含了一条SELECT SQL语句,而且customer1 == customer2 result is true说明两个取出来的对象是同一个对象。其原理是:第一次调用get()方法, Hibernate先检索缓存中是否有该查找对象,发现没有,Hibernate发送SELECT语句到数据库中取出相应的对象,然后将该对象放入缓存中,以便下次使用,第二次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现正好有该查找对象,就从缓存中取出来,不再去数据库中检索。

 

3.hibernate查询缓存

  一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里,查询缓存还存放查询实体对象的id。

查询缓存的生命周期不确定,当它关联的表发生修改,查询缓存的生命周期就结束。

 

  缓存主要是用于查询

//同一个session中,发出两次load方法查询Student student = (Student)session.load(Student.class, 1);System.out.println("student.name=" + student.getName());//不会发出查询语句,load使用缓存student = (Student)session.load(Student.class, 1);System.out.println("student.name=" + student.getName());

 

  第二次查询第一次相同的数据,第二次load方法就是从缓存里取数据,不会发出sql语句到数据库里查询。

//同一个session,发出两次get方法查询Student student = (Student)session.get(Student.class, 1);System.out.println("student.name=" + student.getName());//不会发出查询语句,get使用缓存student = (Student)session.get(Student.class, 1);System.out.println("student.name=" + student.getName());

 

  第二次查询第一次相同的数据,第二次不会发出sql语句查询数据库,而是到缓存里取数据。

//同一个session,发出两次iterate查询实体对象Iterator iter = session.createQuery("from Student s where s.id<5").iterate();while (iter.hasNext()) {Student student = (Student)iter.next();System.out.println(student.getName());}System.out.println("--------------------------------------");//它会发出查询id的语句,但不会发出根据id查询学生的语句,因为iterate使用缓存iter = session.createQuery("from Student s where s.id<5").iterate();while (iter.hasNext()) {Student student = (Student)iter.next();System.out.println(student.getName());}

 

 

   一说到iterater查询就要立刻想起:iterater查询在没有缓存的情况下会有N+1的问题。

执行上面代码查看控制台的sql语句,第一次iterate查询会发出N+1条sql语句,第一条sql语句查询所有的id,然后根据id查询实体对象,有N个id就发出N条语句查询实体。

第二次iterate查询,却只发一条sql语句,查询所有的id,然后根据id到缓存里取实体对象,不再发sql语句到数据库里查询了。

//同一个session,发出两次iterate查询,查询普通属性Iterator iter = session.createQuery("select s.name from Student s where s.id<5").iterate();while (iter.hasNext()) {String name = (String)iter.next();System.out.println(name);}System.out.println("--------------------------------------");//iterate查询普通属性,一级缓存不会缓存,所以发出查询语句//一级缓存是缓存实体对象的iter = session.createQuery("select s.name from Student s where s.id<5").iterate();while (iter.hasNext()) {String name = (String)iter.next();System.out.println(name);}

 

 

  执行代码看控制台sql语句,第一次发出N+1条sql语句,第二次还是发出了N+1条sql语句。因为一级缓存只缓存实体对象,tb不会缓存普通属性,所以第二次还是发出sql查询语句。

//两个session,每个session发出一个load方法查询实体对象try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.load(Student.class, 1);System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}

 

 

  第二个session调用load方法

try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.load(Student.class, 1);//会发出查询语句,session间不能共享一级缓存数据//因为他会伴随着session的消亡而消亡System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}

 

 

  第一个session的load方法会发出sql语句查询实体对象,第二个session的load方法也会发出sql语句查询实体对象。因为session间不能共享一级缓存的数据,所以第二个session的load方法查询相同的数据还是要到数据库中查询,因为它找不到第一个session里缓存的数据。

//同一个session,先调用save方法再调用load方法查询刚刚save的数据Student student = new Student();student.setName("张三");//save方法返回实体对象的idSerializable id = session.save(student);student = (Student)session.load(Student.class, id);//不会发出查询语句,因为save支持缓存System.out.println("student.name=" + student.getName());先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存。当然前提是同一个session。//大批量的数据添加for (int i=0; i<100; i++) {Student student = new Student();student.setName("张三" + i);session.save(student);//每20条更新一次if (i % 20 == 0) {session.flush();//清除缓存的内容session.clear();}}

 

  大批量数据添加时,会造成内存溢出的,因为save方法支持缓存,每save一个对象就往缓存里放,如果对象足够多内存肯定要溢出。一般的做法是先判断一下save了多少个对象,如果save了20个对象就对缓存手动的清理缓存,这样就不会造成内存溢出。

注意:清理缓存前,要手动调用flush方法同步到数据库,否则save的对象就没有保存到数据库里。

注意:大批量数据的添加还是不要使用hibernate,这是hibernate弱项。可以使用jdbc(速度也不会太快,只是比hibernate好一点),或者使用工具产品来实现,比如oracle的Oracle SQL Loader,导入数据特别快。

 

 

 

 

3.二级缓存的管理:

evict(Class arg0, Serializable arg1)将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源。

sessionFactory.evict(Customer.class, new Integer(1));

 

evict(Class arg0)  将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源。

sessionFactory.evict(Customer.class);

 

evictCollection(String arg0)  将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源。

sessionFactory.evictCollection("Customer.orders");

 

 

4.二级缓存的配置

  常用的二级缓存插件

EHCache  org.hibernate.cache.EhCacheProviderOSCache  org.hibernate.cache.OSCacheProviderSwarmCahe  org.hibernate.cache.SwarmCacheProviderJBossCache  org.hibernate.cache.TreeCacheProvider

 

使用方式

hibernate.cfg.xml 配置缓存机制

org.hibernate.cache.EhCacheProvider
true

 

ehcache.xml 缓存机制配置文件<!-- ehcache.xml -->

 

    
  

 

 

 

 

***.hbm.xml 配置二级缓存并发使用策略(开启二级缓存)

  二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。

 

开启二级缓存使用注解的方式

@Entity@Table(name="t_student")@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)  //  表示开启二级缓存,并使用read-only策略public class Student{    private int id;    private String name;    private String sex;    private Classroom room;    .......}

 

 这样我们的二级缓存配置就算完成了,接下来我们来通过测试用例测试下我们的二级缓存是否起作用

 

 

 

hibernate做了一些优化,和一些第三方的缓存产品做了集成。这里介绍采用EHCache缓存产品。

和EHCache二级缓存产品集成:EHCache的jar文件在hibernate的lib里,我们还需要设置一系列的缓存使用策略,需要一个配置文件ehcache.xml来配置。这个文件放在类路径下。

 

我们也可以对某个对象单独配置:

 

还需要在hibernate.cfg.xml配置文件配置缓存,让hibernate知道我们使用的是那个二级缓存。

org.hibernate.cache.EhCacheProvider
true
启用二级缓存的配置可以不写的,因为默认就是true开启二级缓存。必须还手动指定那些实体类的对象放到缓存里在hibernate.cfg.xml里://在
标签里,在
标签后配置

 

或者在实体类映射文件里:

//在
标签里,
标签前配置

 

usage属性表示使用缓存的策略,一般优先使用read-only,表示如果这个数据放到缓存里了,则不允许修改,如果修改就会报错。这就要注意我们放入缓存的数据不允许修改。因为放缓存里的数据经常修改,也就没有必要放到缓存里。

使用read-only策略效率好,因为不能改缓存。但是可能会出现脏数据的问题,这个问题解决方法只能依赖缓存的超时,比如上面我们设置了超时为120秒,120后就可以对缓存里对象进行修改,而在120秒之内访问这个对象可能会查询脏数据的问题,因为我们修改对象后数据库里改变了,而缓存却不能改变,这样造成数据不同步,也就是脏数据的问题。

第二种缓存策略read-write,当持久对象发生变化,缓存里就会跟着变化,数据库中也改变了。这种方式需要加解锁,效率要比第一种慢。

还有两种策略,请看hibernate文档,最常用还是第一二种策略。

二级缓存测试代码演示:注意上面我们讲的两个session分别调用load方法查询相同的数据,第二个session的load方法还是发了sql语句到数据库查询数据,这是因为一级缓存只在当前session中共享,也就是说一级缓存不能跨session访问。

//开启二级缓存,二级缓存是进程级的缓存,可以共享//两个session分别调用load方法查询相同的实体对象try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.load(Student.class, 1);System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.load(Student.class, 1);//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据//二级缓存是进程级的缓存System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}

 

如果开启了二级缓存,那么第二个session调用的load方法查询第一次查询的数据,是不会发出sql语句查询数据库的,而是去二级缓存中取数据。

//开启二级缓存//两个session分别调用get方法查询相同的实体对象try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.get(Student.class, 1);System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}try {session = HibernateUtils.getSession();session.beginTransaction();Student student = (Student)session.get(Student.class, 1);//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据//二级缓存是进程级的缓存System.out.println("student.name=" + student.getName());session.getTransaction().commit();}catch(Exception e) {e.printStackTrace();session.getTransaction().rollback();}finally {HibernateUtils.closeSession(session);}

 

注意:二级缓存必须让sessionfactory管理,让sessionfactory来清除二级缓存。

sessionFactory.evict(Student.class);//清除二级缓存中所有student对象,sessionFactory.evict(Student.class,1);//清除二级缓存中id为1的student对象。

 

如果在第一个session调用load或get方法查询数据后,把二级缓存清除了,那么第二个session调用load或get方法查询相同的数据时,还是会发出sql语句查询数据库的,因为缓存里没有数据只能到数据库里查询。

我们查询数据后会默认自动的放到二级和一级缓存里,如果我们想查询的数据不放到缓存里,也是可以的。也就是说我们可以控制一级缓存和二级缓存的交换。

session.setCacheMode(CacheMode.IGNORE);//禁止将一级缓存中的数据往二级缓存里放。

 

 

还是用上面代码测试,在第一个session调用load方法前,执行session.setCacheMode(CacheMode.IGNORE);这样load方法查询的数据不会放到二级缓存里。那么第二个session执行load方法查询相同的数据,会发出sql语句到数据库中查询,因为二级缓存里没有数据,一级缓存因为不同的session不能共享,所以只能到数据库里查询。

上面我们讲过大批量的数据添加时可能会出现溢出,解决办法是每当天就20个对象后就清理一次一级缓存。如果我们使用了二级缓存,光清理一级缓存是不够的,还要禁止一二级缓存交互,在save方法前调用session.setCacheMode(CacheMode.IGNORE)。

二级缓存也不会存放普通属性的查询数据,这和一级缓存是一样的,只存放实体对象。session级的缓存对性能的提高没有太大的意义,因为生命周期太短了。

 

实例 测试:

TestCase1

public class TestSecondCache{    @Test    public void testCache1()    {        Session session = null;        try        {            session = HibernateUtil.openSession();            Student stu = (Student) session.load(Student.class, 1);            System.out.println(stu.getName() + "-----------");        }        catch (Exception e)        {            e.printStackTrace();        }        finally        {            HibernateUtil.close(session);        }        try        {            /**             * 即使当session关闭以后,因为配置了二级缓存,而二级缓存是sessionFactory级别的,所以会从缓存中取出该数据             * 只会发出一条sql语句             */            session = HibernateUtil.openSession();            Student stu = (Student) session.load(Student.class, 1);            System.out.println(stu.getName() + "-----------");            /**             * 因为设置了二级缓存为read-only,所以不能对其进行修改             */            session.beginTransaction();            stu.setName("aaa");            session.getTransaction().commit();        }        catch (Exception e)        {            e.printStackTrace();            session.getTransaction().rollback();        }        finally        {            HibernateUtil.close(session);        }    }

 

打印的sql

Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?aaa-----------aaa-----------

 

  因为二级缓存是sessionFactory级别的缓存,我们看到,在配置了二级缓存以后,当我们session关闭以后,我们再去查询对象的时候,此时hibernate首先会去二级缓存中查询是否有该对象,有就不会再发sql了

  二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去

@Test    public void testCache2()    {        Session session = null;        try        {            session = HibernateUtil.openSession();            /**             * 注意:二级缓存中缓存的仅仅是对象,而下面这里只保存了姓名和性别两个字段,所以 不会被加载到二级缓存里面             */            List
ls = (List
) session .createQuery("select stu.name, stu.sex from Student stu") .setFirstResult(0).setMaxResults(30).list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 由于二级缓存缓存的是对象,所以此时会发出两条sql */ session = HibernateUtil.openSession(); Student stu = (Student) session.load(Student.class, 1); System.out.println(stu); } catch (Exception e) { e.printStackTrace(); } }

 

打印的sql

Hibernate: select student0_.name as col_0_0_, student0_.sex as col_1_0_ from t_student student0_ limit ?Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?

 

看到这个测试用例,如果我们只是取出对象的一些属性的话,则不会将其保存到二级缓存中去,因为二级缓存缓存的仅仅是对象

 

  通过二级缓存来解决n+1

@Test    public void testCache3()    {        Session session = null;        try        {            session = HibernateUtil.openSession();            /**             * 将查询出来的Student对象缓存到二级缓存中去             */            List
stus = (List
) session.createQuery( "select stu from Student stu").list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条 * 取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题 * 而且内存占用也不多 */ session = HibernateUtil.openSession(); Iterator
iterator = session.createQuery("from Student") .iterate(); for (; iterator.hasNext();) { Student stu = (Student) iterator.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } }

 

当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题

 

  二级缓存不缓存HQL语句

@Test    public void testCache4()    {        Session session = null;        try        {            session = HibernateUtil.openSession();            List
ls = session.createQuery("from Student") .setFirstResult(0).setMaxResults(50).list(); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 使用List会发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存 */ session = HibernateUtil.openSession(); List
ls = session.createQuery("from Student") .setFirstResult(0).setMaxResults(50).list(); Iterator
stu = ls.iterator(); for(;stu.hasNext();) { Student student = stu.next(); System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }

 

打印的sql

Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?

看到,当我们如果通过 list() 去查询两次对象时,二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句,要想解决这个问题,我们就要配置我们的查询缓存了。

 

 

 

 

 

  hibernate 查询缓存

  一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里,查询缓存还存放查询实体对象的id。

查询缓存的生命周期不确定,当它关联的表发生修改,查询缓存的生命周期就结束。这里表的修改指的是通过hibernate修改,并不是通过数据库客户端软件登陆到数据库上修改。

hibernate的查询缓存默认是关闭的,如果要使用就要到hibernate.cfg.xml文件里配置:

true

 

 

并且必须在程序中手动启用查询缓存,在query接口中的setCacheable(true)方法来启用。

//关闭二级缓存,没有开启查询缓存,采用list方法查询普通属性//同一个sessin,查询两次List names = session.createQuery("select s.name from Student s").list();for (int i=0; i
.list(); for (int i=0; i

 

上面代码运行,由于没有使用查询缓存,而一、二级缓存不会缓存普通属性,所以第二次查询还是会发出sql语句到数据库中查询。

现在开启查询缓存,关闭二级缓存,并且在第一次的list方法前调用setCacheable(true),并且第二次list查询前也调用这句代码,可以写出下面这样:

List names = session.createQuery("select s.name from Student s").setCacheable(true).list();

 

其它代码不变,运行代码后发现第二次list查询普通属性没有发出sql语句,也就是说没有到数据库中查询,而是到查询缓存中取数据。

//开启查询缓存,关闭二级缓存,采用list方法查询普通属性//在两个session中调用list方法try {session = HibernateUtils.getSession();session.beginTransaction();List names = session.createQuery("select s.name from Student s").setCacheable(true).list();for (int i=0; i

 

运行结果是第二个session发出的list方法查询普通属性,没有发出sql语句到数据库中查询,而是到查询缓存里取数据,这说明查询缓存和session生命周期没有关系。

//开启缓存,关闭二级缓存,采用iterate方法查询普通属性//在两个session中调用iterate方法查询

 

运行结果是第二个session的iterate方法还是发出了sql语句查询数据库,这说明iterate迭代查询普通属性不支持查询缓存。

//关闭查询缓存,关闭二级缓存,采用list方法查询实体对象//在两个session中调用list方法查询

 

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据,因为关闭了二级缓存,所以第二个session调用list方法查询实体对象,还是会发出sql语句到数据库中查询。

//开启查询缓存,关闭二级缓存//在两个session中调用list方法查询实体对象

 

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据库的。第二个session调用list方法查询实体对象,却发出了很多sql语句查询数据库,这跟N+1的问题是一样的,发出了N+1条sql语句。为什么会出现这样的情况呢?这是因为我们现在查询的是实体对象,查询缓存会把第一次查询的实体对象的id放到缓存里,当第二个session再次调用list方法时,它会到查询缓存里把id一个一个的拿出来,然后到相应的缓存里找(先找一级缓存找不到再找二级缓存),如果找到了就返回,如果还是没有找到,则会根据一个一个的id到数据库中查询,所以一个id就会有一条sql语句。

注意:如果配置了二级缓存,则第一次查询实体对象后,会往一级缓存和二级缓存里都存放。如果没有二级缓存,则只在一级缓存里存放。(一级缓存不能跨session共享)

//开启查询缓存,开启二级缓存//在两个session中调用list方法查询实体对象

 

运行结果是第一个session调用list方法会发出sql语句到数据库里查询实体对象,因为配置了二级缓存,则实体对象会放到二级缓存里,因为配置了查询缓存,则实体对象所有的id放到了查询缓存里。第二个session调用list方法不会发出sql语句,而是到二级缓存里取数据。

查询缓存意义不大,查询缓存说白了就是存放由list方法或iterate方法查询的数据。我们在查询时很少出现完全相同条件的查询,这也就是命中率低,这样缓存里的数据总是变化的,所以说意义不大。除非是多次查询都是查询相同条件的数据,也就是说返回的结果总是一样,这样配置查询缓存才有意义。

 

如果是在annotation中,我们还需要在这个类上加上这样一个注解:@Cacheable

接下来我们来通过测试用例来看看我们的查询缓存

①查询缓存也是sessionFactory级别的缓存

@Test    public void test2() {        Session session = null;        try {            /**             * 此时会发出一条sql取出所有的学生信息             */            session = HibernateUtil.openSession();            List
ls = session.createQuery("from Student") .setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存 .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } try { /** * 此时会发出一条sql取出所有的学生信息 */ session = HibernateUtil.openSession(); List
ls = session.createQuery("from Student") .setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存 .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }

 

打印的sql

Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?

 

看到,此时如果我们发出两条相同的语句,hibernate也只会发出一条sql,因为已经开启了查询缓存了,并且查询缓存也是sessionFactory级别的

②只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效

@Test    public void test3() {        Session session = null;        try {            /**             * 此时会发出一条sql取出所有的学生信息             */            session = HibernateUtil.openSession();            List
ls = session.createQuery("from Student where name like ?") .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } session = null; try { /** * 此时会发出一条sql取出所有的学生信息 */ session = HibernateUtil.openSession(); /** * 只有当HQL完全相同的时候,连参数都要相同,查询缓存才有效 */// List
ls = session.createQuery("from Student where name like ?")// .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存// .setParameter(0, "%王%")// .setFirstResult(0).setMaxResults(50).list(); List
ls = session.createQuery("from Student where name like ?") .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存 .setParameter(0, "%张%") .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }

 

打印的sql

Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ?Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ?

 

看到,如果我们的hql查询语句不同的话,我们的查询缓存也没有作用

③查询缓存也能引起 N+1 的问题

查询缓存也能引起 N+1 的问题,我们这里首先先将 Student 对象上的二级缓存先注释掉:

 
@Test    public void test4() {        Session session = null;        try {            /**             * 查询缓存缓存的不是对象而是id             */            session = HibernateUtil.openSession();            List
ls = session.createQuery("from Student where name like ?") .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } session = null; try { /** * 查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是缓存了 * id,所以此处会发出大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因 * 所以如果使用查询缓存必须开启二级缓存 */ session = HibernateUtil.openSession(); List
ls = session.createQuery("from Student where name like ?") .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator
stus = ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }

 

打印的sql

Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ where student0_.name like ? limit ?Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_student student0_ left outer join t_classroom classroom1_ on student0_.rid=classroom1_.id left outer join t_special special2_ on classroom1_.sid=special2_.id where student0_.id=?.........................

 

我们看到,当我们将二级缓存注释掉以后,在使用查询缓存时,也会出现 N+1 的问题,为什么呢?

因为查询缓存缓存的也仅仅是对象的id,所以第一条 sql 也是将对象的id都查询出来,但是当我们后面如果要得到每个对象的信息的时候,此时又会发sql语句去查询,所以,如果要使用查询缓存,我们一定也要开启我们的二级缓存,这样就不会出现 N+1 问题了

 

 

 

 

若存在一对多的关系,想要在在获取一方的时候将关联的多方缓存起来,需要在集合属性下添加<cache>子标签,这里需要将关联的对象的hbm文件中必须在存在<class>标签下也添加<cache>标签,不然Hibernate只会缓存OID。

 

转载于:https://www.cnblogs.com/hwaggLee/p/4438003.html

你可能感兴趣的文章
作业完成2
查看>>
PHP截取中英文混合字符
查看>>
HTA - OnKeyDown
查看>>
【洛谷P1816 忠诚】线段树
查看>>
CDN 学习笔记
查看>>
电子眼抓拍大解密
查看>>
Linux系统下 /etc/shadow 档案结构
查看>>
多线程---线程间的通信
查看>>
poj 1331 Multiply
查看>>
严重: 文档无效: 找不到语法。 at (null:2:19)
查看>>
tomcat7的数据库连接池tomcatjdbc的25个优势
查看>>
Html 小插件5 百度搜索代码2
查看>>
nodejs-Path模块
查看>>
python学习(二十二) String(上)
查看>>
oracle函数 NLS_LOWER(x[,y])
查看>>
free内存监控
查看>>
Bean in Configuration Or Component
查看>>
JavaScript基础知识梳理----正则表达式
查看>>
USACO Training完结感想
查看>>
python-19 随机模块 random
查看>>