http://www.cnblogs.com/xiaoluo501395377/p/3377604.html html
在本篇随笔里将会分析一下hibernate的缓存机制,包括一级缓存(session级别)、二级缓存(sessionFactory级别)以及查询缓存,固然还要讨论下咱们的N+1的问题。java
随笔虽长,但我相信看完的朋友绝对能对hibernate的 N+1问题以及缓存有更深的了解。sql
1、N+1问题数据库
首先咱们来探讨一下N+1的问题,咱们先经过一个例子来看一下,什么是N+1问题:缓存
list()得到对象:session
/** * 此时会发出一条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语句,将全部的对象查询出来,这点相信你们都能理解app
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?
那么,咱们再来看看iterator()这种状况ide
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的一级缓存。
2、一级缓存(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级别的缓存。
3、二级缓存(sessionFactory级别)
使用hibernate二级缓存,咱们首先须要对其进行配置,配置步骤以下:
1.hibernate并无提供相应的二级缓存的组件,因此须要加入额外的二级缓存包,经常使用的二级缓存包是EHcache。这个咱们在下载好的hibernate的lib->optional->ehcache下能够找到(我这里使用的hibernate4.1.7版本),而后将里面的几个jar包导入便可。
2.在hibernate.cfg.xml配置文件中配置咱们二级缓存的一些属性:
<!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二级缓存的提供类 在hibernate4.0版本之后咱们都是配置这个属性来指定二级缓存的提供类--> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property> <!-- 二级缓存配置文件的位置 --> <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
我这里使用的是hibernate4.1.7版本,若是是使用hibernate3的版本的话,那么二级缓存的提供类则要配置成这个:
<!--这个类在4.0版本之后已经不建议被使用了-->
<property name="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" //是否永久保存缓存,设置成false timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" //若是对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true />
<!--
一、timeToLiveSeconds的定义是:以建立时间为基准开始计算的超时时长;
二、timeToIdleSeconds的定义是:在建立时间和最近访问时间中取出离如今最近的时间做为基准计算的超时时长;
三、若是仅设置了timeToLiveSeconds,则该对象的超时时间=建立时间+timeToLiveSeconds,假设为A;
四、若是没设置timeToLiveSeconds,则该对象的超时时间=max(建立时间,最近访问时间)+timeToIdleSeconds,假设为B;
五、若是二者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成当即算超时。
-->
<!--能够给每一个实体类指定一个配置文件,经过name属性指定,要使用类的全名--> <cache name="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这个类上加上这样一个注解:
@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; ....... }
这样咱们的二级缓存配置就算完成了,接下来咱们来经过测试用例测试下咱们的二级缓存是否起做用
①二级缓存是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查询语句,要想解决这个问题,咱们就要配置咱们的查询缓存了。
4、查询缓存(sessionFactory级别)
咱们若是要配置查询缓存,只须要在hibernate.cfg.xml中加入一条配置便可:
<!-- 开启查询缓存 --> <property name="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 问题了