建站资讯

深层次分析hibernate的N+1难题缓和存难题

作者:admin 发布时间:2021-04-03

在这篇随笔里可能剖析一下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中查寻2次数据信息,假如应用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之中要2次查寻出许多目标,这时大家假如写两根 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..hibernate.cache.ehcache.EhCacheRegionFactory /property   !-- 二级缓存文件配备文档的部位 --   propertyname= hibernate.cache.provider_configuration_file_resource_path ehcache.xml /property 
复制代码

我这儿应用的是hibernate4.1.7版本号,假如是应用hibernate3的版本号得话,那麼二级缓存文件的出示类则要配备成这一:

 !--这一类在4.0版本号之后早已不提议被应用了-- 
propertyname= hibernate..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  >
复制代码

二级缓存文件的应用对策一般有这几类:read-only、nonstrict-read-write、read-write、transactional。留意:大家一般应用二级缓存文件全是将其配备成 read-only ,即大家理应在哪些不用开展改动的实体线类上应用二级缓存文件,不然假如对缓存文件开展读写能力得话,特性能变差,那样设定缓存文件就丧失了实际意义。

②假如应用annotation配备,大家必须在Student这一类上添上那样一个注释:

复制代码
@= 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(); /** * 留意:二级缓存文件中缓存文件的只是是目标,而下边这儿只储存了名字和性別2个字段名,因此 不容易被载入到二级缓存文件里边 */ 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(); } }
复制代码

当我们们假如必须查寻出2次目标的情况下,可使用二级缓存文件来处理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() 去查寻2次目标时,二级缓存文件尽管会缓存文件查寻出去的目标,可是大家见到传出了两根同样的查寻句子,它是由于二级缓存文件不容易缓存文件大家的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 难题了

 好啦,整篇文章随笔大约花销了两个钟头来撰写,能够说将hibernate的 N+1 难题、一级缓存文件、二级缓存文件、查寻缓存文件的定义及其将会出現的难题都剖析了透,期待能对大伙儿出示协助!


收缩