zoukankan      html  css  js  c++  java
  • hibernate缓存机制与N+1问题

    在项目中遇到的趣事

    本文基于hibernate缓存机制与N+1问题展开思考,

    先介绍何为N+1问题

    再hibernate中用list()获得对象:

     1 /**
     2              * 此时会发出一条sql,将30个学生全部查询出来
     3              */
     4             List<Student> ls = (List<Student>)session.createQuery("from Student")
     5                                 .setFirstResult(0).setMaxResults(30).list();
     6             Iterator<Student> stus = ls.iterator();
     7             for(;stus.hasNext();)
     8             {
     9                 Student stu = (Student)stus.next();
    10                 System.out.println(stu.getName());
    11             }

    控制台输出:

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

    如果通过list()方法来获得对象,毫无疑问,hibernate会发出一条sql语句,将所有的对象查询出来,这没什么问题。

    用iterator()这种情况:

     1 /**
     2              * 如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql
     3              * 在查询相应的具体的某个学生信息时,会再发出相应的SQL去取学生信息
     4              * 这就是典型的N+1问题
     5              * 存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来
     6              * 而是要iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
     7              */
     8             Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student")
     9                                 .setFirstResult(0).setMaxResults(30).iterate();
    10             for(;stus.hasNext();)
    11             {
    12                 Student stu = (Student)stus.next();
    13                 System.out.println(stu.getName());
    14             }

    在执行完上述的测试用例后,我们来看看控制台的输出,看会发出多少条 sql 语句:

    Hibernate: select student0_.id as col_0_0_ from t_student student0_ limit ?
    Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
    张一
    Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
    肇庆
    Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
    桑耳
    .........

    我们看到,当如果通过iterator()方法来获得我们对象的时候,hibernate首先会发出1条sql去查询出所有对象的 id 值当我们如果需要查询到某个对象的具体信息的时候,hibernate此时会根据查询出来的 id 值再发sql语句去从数据库中查询对象的信息这就是典型的 N+1 的问题。(简单来说就是会有多一次去数据库中查询,解决思路很简单,把那多余的一次的查询不在数据库中执行就可以了)

           那么这种 N+1 问题我们如何解决呢,其实我们只需要使用 list() 方法来获得对象即可。

          但是既然可以通过 list() 我们就不会出现 N+1的问题,那么我们为什么还要保留 iterator()这种形式呢? 嗯..............存在即合理。想想有没有特殊的情况能发挥 iterator()的优势????

      如果我们需要在一个session当中要两次查询出很多对象,此时我们如果写两条 list()时,hibernate此时会发出两条 sql 语句,而且这两条语句是一样的,

      但是我们如果第一条语句使用 list(),而第二条语句使用 iterator()的话,此时我们也会发两条sql语句,但是第二条语句只会将查询出对象的id,所以相对应取出所有的对象而已,显然这样可以节省内存和减少与数据库的交互(提升效率),而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。这里就牵涉到了接下来这个概念:hibernate的一级缓存。

    二、一级缓存(session级别)

    我们来看看hibernate提供的一级缓存

    1 /**
    2              * 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中
    3              * 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
    4              * 这就是hibernate的一级缓存(session缓存)
    5              */
    6             List<Student> stus = (List<Student>)session.createQuery("from Student")
    7                                     .setFirstResult(0).setMaxResults(30).list();
    8             Student stu = (Student)session.load(Student.class, 1);

    我们来看看控制台输出:

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

    我们看到此时hibernate仅仅只会发出一条 sql 语句,因为第一行代码就会将整个的对象查询出来,放到session的一级缓存中去,当我如果需要再次查询学生对象时,此时首先会去缓存中看是否存在该对象,如果存在,则直接从缓存中取出,就不会再发sql了,但是要注意一点:hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库

     1 try
     2         {
     3             session = HibernateUtil.openSession();
     4             
     5             /**
     6              * 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中
     7              * 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
     8              * 这就是hibernate的一级缓存(session缓存)
     9              */
    10             List<Student> stus = (List<Student>)session.createQuery("from Student")
    11                                     .setFirstResult(0).setMaxResults(30).list();
    12             Student stu = (Student)session.load(Student.class, 1);
    13             System.out.println(stu.getName() + "-----------");
    14         }
    15         catch (Exception e)
    16         {
    17             e.printStackTrace();
    18         }
    19         finally
    20         {
    21             HibernateUtil.close(session);
    22         }
    23         /**
    24          * 当session关闭以后,session的一级缓存也就没有了,这时就又会去数据库中查询
    25          */
    26         session = HibernateUtil.openSession();
    27         Student stu = (Student)session.load(Student.class, 1);
    28         System.out.println(stu.getName() + "-----------");
    1 Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_, student0_.rid as rid2_ from t_student student0_ limit ?
    2 
    3 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=?

    我们看到此时会发出两条sql语句,因为session关闭以后,一级缓存就不存在了,所以如果再查询的时候,就会再发sql。要解决这种问题,我们应该怎么做呢?若按空间换取时间的思路,那能不能再来一个缓存?这就要我们来配置hibernate的二级缓存了,也就是sessionFactory级别的缓存。

    三、二级缓存(sessionFactory级别)(简单介绍)

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

    由于学生对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条取id的语句,
    然后在获取去二级缓存中对象时,如果发现就不会再发SQL,这样也就解决了N+1问题 而且内存占用也不多。

    万千丛中一点绿, 对象!,对象!,对象! 二级缓存就把第一次对象查询拦截了,解决了N+1问题

  • 相关阅读:
    Mybatis多表查询
    (转)Java安全通信:HTTPS与SSL
    (转)RSA加密解密及数字签名Java实现
    (转)大型企业电话会议视频会议备份解决方案
    (转)虚拟IP原理
    虚拟IP---Linux下一个网卡配置多个IP
    C++ 点
    算法(8)Maximum Product Subarray
    算法(7)Majority Element II
    算法(6)3Sum Closest
  • 原文地址:https://www.cnblogs.com/smellpawn/p/10809738.html
Copyright © 2011-2022 走看看