深化分析hibernate的N+1问题和缓存问题

http://www.ndnha2bn.com/  2013-10-27 14:09:20  来历:澳门彩票官网网 

Webjx澳门彩票官网提示:hibernate缓存机制详细分析.

在本篇漫笔里将会分析一下hibernate的缓存机制,包含一级缓存(session等级)、二级缓存(sessionFactory等级)以及查询缓存,当然还要评论下咱们的N+1的问题。

漫笔虽长,但我信任看完的朋友肯定能对hibernate的 N+1问题以及缓存有更深的了解。

一、N+1问题

首要咱们来讨论一下N+1的问题,咱们先经过一个比如来看一下,什么是N+1问题:

list()取得目标:

仿制代码
       /** * 此刻会宣布一条sql,将30个学生悉数查询出来 */ List<Student> ls = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Iterator<Student> stus =ls.iterator(); for(;stus.hasNext();) { Student stu = (Student)stus.next(); System.out.println(stu.getName()); }
仿制代码

假如经过list()办法来取得目标,毫无疑问,hibernate会宣布一条sql句子,将一切的目标查询出来,这点信任咱们都能了解

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

那么,咱们再来看看iterator()这种状况

iterator()取得目标

仿制代码
       /** * 假如运用iterator办法回来列表,关于hibernate而言,它仅仅仅仅宣布取id列表的sql * 在查询相应的详细的某个学生信息时,会宣布相应的SQL去取学生信息 * 这便是典型的N+1问题 * 存在iterator的原因是,有或许会在一个session中查询两次数据,假如运用list每一次都会把一切的目标查询上来 * 而是要iterator仅仅只会查询id,此刻一切的目标现已存储在一级缓存(session的缓存)中,能够直接获取 */ Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).iterate(); for(;stus.hasNext();) { Student stu = (Student)stus.next(); System.out.println(stu.getName()); }
仿制代码

在履行完上述的测验用例后,咱们来看看控制台的输出,看会宣布多少条 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()这种办法呢?咱们考虑这样一种状况,假如咱们需求在一个session傍边要两次查询出许多目标,此刻咱们假如写两条 list()时,hibernate此刻会宣布两条 sql 句子,而且这两条句子是相同的,可是咱们假如榜首条句子运用 list(),而第二条句子运用 iterator()的话,此刻咱们也会发两条sql句子,可是第二条句子只会将查询出目标的id,所以相对应取出一切的目标罢了,明显这样能够节约内存,而假如再要获取目标的时分,由于榜首条句子现已将目标都查询出来了,此刻会将目标保存到session的一级缓存中去,所以再次查询时,就会首要去缓存中查找,假如找到,则不发sql句子了。这儿就牵涉到了接下来这个概念:hibernate的一级缓存。

二、一级缓存(session等级)

咱们来看看hibernate供给的一级缓存:

仿制代码
       /** * 此刻会宣布一条sql,将一切学生悉数查询出来,并放到session的一级缓存傍边 * 当再次查询学生信息时,会首要去缓存中看是否存在,假如不存在,再去数据库中查询 * 这便是hibernate的一级缓存(session缓存) */ List<Student> stus = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Student stu = (Student)session.load(Student.class, 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去查数据库

仿制代码
     try { session = HibernateUtil.openSession(); /** * 此刻会宣布一条sql,将一切学生悉数查询出来,并放到session的一级缓存傍边 * 当再次查询学生信息时,会首要去缓存中看是否存在,假如不存在,再去数据库中查询 * 这便是hibernate的一级缓存(session缓存)*/ List<Student> stus = (List<Student>)session.createQuery("from Student") .setFirstResult(0).setMaxResults(30).list(); Student stu = (Student)session.load(Student.class, 1); System.out.println(stu.getName() + "-----------"); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } /** * 当session封闭今后,session的一级缓存也就没有了,这时就又会去数据库中查询 */ session = HibernateUtil.openSession(); Student stu = (Student)session.load(Student.class, 1); System.out.println(stu.getName() + "-----------");
仿制代码
仿制代码
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_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等级)

运用hibernate二级缓存,咱们首要需求对其进行装备,装备过程如下:

1.hibernate并没有供给相应的二级缓存的组件,所以需求参加额定的二级缓存包,常用的二级缓存包是EHcache。这个咱们在下载好的hibernate的lib->optional->ehcache下能够找到(我这儿运用的hibernate4.1.7版别),然后将里边的几个jar包导入即可。

2.在hibernate.cfg.xml装备文件中装备咱们二级缓存的一些特点:

仿制代码
     <!-- 敞开二级缓存 --> <propertyname="hibernate.cache.use_second_level_cache">true</property> <!-- 二级缓存的供给类 在hibernate4.0版别今后咱们都是装备这个特点来指定二级缓存的供给类--><propertyname="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- 二级缓存装备文件的方位 --> <propertyname="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
仿制代码

我这儿运用的是hibernate4.1.7版别,假如是运用hibernate3的版别的话,那么二级缓存的供给类则要装备成这个:

<!--这个类在4.0版别今后现已不主张被运用了-->
<
propertyname="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>

3.装备hibernate的二级缓存是经过运用 ehcache的缓存包,所以咱们需求创立一个 ehcache.xml 的装备文件,来装备咱们的缓存信息,将其放到项目根目录下

仿制代码
<ehcache> <!-- Sets the path to the directory where cache .data files are created. If the path is a Java System Property it is replaced by its value in the running VM. The following properties are translated: user.home - User's home directory user.dir - User's current working directory java.io.tmpdir - Default temp file path -->
  
  <!--指定二级缓存寄存在磁盘上的方位--> <diskStore path="user.dir"/>     <!--咱们能够给每个实体类指定一个对应的缓存,假如没有匹配到该类,则运用这个默许的缓存装备--> <defaultCache maxElementsInMemory="10000"  //在内存中寄存的最大目标数eternal="false"         //是否永久保存缓存,设置成falsetimeToIdleSeconds="120"     timeToLiveSeconds="120"    overflowToDisk="true"     //假如目标数量超越内存中最大的数,是否将其保存到磁盘中,设置成true />   
  <!--

    1、timeToLiveSeconds的界说是:以创立时刻为基准开端核算的超时时长;
    2、timeToIdleSeconds的界说是:在创立时刻和最近拜访时刻中取出离现在最近的时刻作为基准核算的超时时长;
    3、假如仅设置了timeToLiveSeconds,则该目标的超时时刻=创立时刻+timeToLiveSeconds,假设为A;
    4、假如没设置timeToLiveSeconds,则该目标的超时时刻=max(创立时刻,最近拜访时刻)+timeToIdleSeconds,假设为B;
    5、假如两者都设置了,则取出A、B最少的值,即min(A,B),表明只需有一个超时成当即算超时。

  -->
  <!--能够给每个实体类指定一个装备文件,经过name特点指定,要运用类的全名--> <cachename="com.xiaoluo.bean.Student" maxElementsInMemory="10000" eternal="false"timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /><cache name="sampleCache2" maxElementsInMemory="1000" eternal="true"timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> --></ehcache>
仿制代码

4.敞开咱们的二级缓存

①假如运用xml装备,咱们需求在 Student.hbm.xml 中加上一下装备:

仿制代码
<hibernate-mapping package="com.xiaoluo.bean"> <class name="Student" table="t_student"> <!-- 二级缓存一般设置为只读的 --> <cache usage="read-only"/> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" column="name" type="string"></property> <property name="sex" column="sex" type="string"></property> <many-to-one name="room" column="rid" fetch="join"></many-to-one> </class> </hibernate-mapping>
仿制代码

二级缓存的运用战略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。留意:咱们一般运用二级缓存都是将其装备成 read-only ,即咱们应当在那些不需求进行修正的实体类上运用二级缓存,不然假如对缓存进行读写的话,功用会变差,这样设置缓存就失去了含义。

②假如运用annotation装备,咱们需求在Student这个类上加上这样一个注解:

仿制代码
@[email protected](name="t_student")@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)  //  表明敞开二级缓存,并运用read-only战略public class Student{ privateint id; private String name; private String sex; private Classroom room; .......}
仿制代码

这样咱们的二级缓存装备就算完成了,接下来咱们来经过测验用例测验下咱们的二级缓存是否起效果

①二级缓存是sessionFactory等级的缓存

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); } }
仿制代码
仿制代码
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了。

②二级缓存缓存的仅仅是目标,假如查询出来的是目标的一些特点,则不会被加到缓存中去

TestCase2:

仿制代码
  @Test public void testCache2() { Session session = null; try { session =HibernateUtil.openSession(); /** * 留意:二级缓存中缓存的仅仅是目标,而下面这儿只保存了名字和性别两个字段,所以 不会被加载到二级缓存里边 */ List<Object[]> ls = (List<Object[]>) 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(); } }
仿制代码
仿制代码
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 的问题

TestCase3:

仿制代码
  @Test public void testCache3() { Session session = null; try { session =HibernateUtil.openSession(); /** * 将查询出来的Student目标缓存到二级缓存中去 */List<Student> stus = (List<Student>) 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<Student> 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 句子吗?

TestCase4:

仿制代码
  @Test public void testCache4() { Session session = null; try { session =HibernateUtil.openSession(); List<Student> 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<Student> ls = session.createQuery("from Student") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stu = ls.iterator(); for(;stu.hasNext();) { Student student = stu.next(); System.out.println(student.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
仿制代码
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查询句子,要想处理这个问题,咱们就要装备咱们的查询缓存了。

四、查询缓存(sessionFactory等级)

咱们假如要装备查询缓存,只需求在hibernate.cfg.xml中参加一条装备即可:

     <!-- 敞开查询缓存 --> <propertyname="hibernate.cache.use_query_cache">true</property>

然后咱们假如在查询hql句子时要运用查询缓存,就需求在查询句子后边设置这样一个办法:

List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)  //敞开查询缓存,查询缓存也是SessionFactory等级的缓存.setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list();

假如是在annotation中,咱们还需求在这个类上加上这样一个注解:@Cacheable

接下来咱们来经过测验用例来看看咱们的查询缓存

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

TestCase1:

仿制代码
  @Test public void test2() { Session session = null; try { /** * 此刻会宣布一条sql取出一切的学生信息 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student") .setCacheable(true)//敞开查询缓存,查询缓存也是sessionFactory等级的缓存.setFirstResult(0).setMaxResults(50).list(); Iterator<Student> 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<Student> ls = session.createQuery("from Student") .setCacheable(true)//敞开查询缓存,查询缓存也是sessionFactory等级的缓存.setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus =ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
仿制代码
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 查询句子完全相同时,连参数设置都要相同,此刻查询缓存才有用

TestCase2:

仿制代码
  @Test public void test3() { Session session = null; try { /** * 此刻会宣布一条sql取出一切的学生信息 */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//敞开查询缓存,查询缓存也是SessionFactory等级的缓存.setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> 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<Student> ls = session.createQuery("from Student where name like ?")// .setCacheable(true)//敞开查询缓存,查询缓存也是SessionFactory等级的缓存// .setParameter(0, "%王%")// .setFirstResult(0).setMaxResults(50).list();List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//敞开查询缓存,查询缓存也是SessionFactory等级的缓存.setParameter(0, "%张%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus = ls.iterator(); for(;stus.hasNext();) { Student stu =stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
仿制代码
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 目标上的二级缓存先注释掉:

     <!-- 二级缓存一般设置为只读的 --> <!--  <cache usage="read-only"/>  -->

TestCase4:

仿制代码
  @Test public void test4() { Session session = null; try { /** * 查询缓存缓存的不是目标而是id */ session = HibernateUtil.openSession(); List<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//敞开查询缓存,查询缓存也是SessionFactory等级的缓存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> 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<Student> ls = session.createQuery("from Student where name like ?") .setCacheable(true)//敞开查询缓存,查询缓存也是SessionFactory等级的缓存 .setParameter(0, "%王%") .setFirstResult(0).setMaxResults(50).list(); Iterator<Student> stus =ls.iterator(); for(;stus.hasNext();) { Student stu = stus.next(); System.out.println(stu.getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.close(session); } }
仿制代码
仿制代码
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 问题了

 好了,整篇漫笔大约花费了2个小时来编写,能够说将hibernate的 N+1 问题、一级缓存、二级缓存、查询缓存的概念以及或许呈现的问题都分析了透,期望能对咱们供给协助!

更多

引荐文章