【hibernate系列】hibernate的n+1问题

什么叫n+1次select查询问题?数据库

在Session的缓存中存放的是相互关联的对象图。默认状况下,当Hibernate从数据库中加载Customer对象时,会同时加载全部关联的Order对象。以Customer和Order类为例,假定ORDERS表的CUSTOMER_ID外键容许为null,图1列出了CUSTOMERS表和ORDERS表中的记录。



如下Session的find()方法用于到数据库中检索全部的Customer对象:

List customerLists=session.find("from Customer as c");

运行以上find()方法时,Hibernate将先查询CUSTOMERS表中全部的记录,而后根据每条记录的ID,到ORDERS表中查询有参照关系的记录,Hibernate将依次执行如下select语句:

select * from CUSTOMERS; 
select * from ORDERS where CUSTOMER_ID=1;
select * from ORDERS where CUSTOMER_ID=2;
select * from ORDERS where CUSTOMER_ID=3;
select * from ORDERS where CUSTOMER_ID=4;

经过以上5条select语句,Hibernate最后加载了4个Customer对象和5个Order对象,在内存中造成了一幅关联的对象图,参见图2。数组



Hibernate在检索与Customer关联的Order对象时,使用了默认的当即检索策略。这种检索策略存在两大不足:

(1) select语句的数目太多,须要频繁的访问数据库,会影响检索性能。若是须要查询n个Customer对象,那么必须执行n+1次select查询语句。这就是经典的n+1次select查询问题。这种检索策略没有利用SQL的链接查询功能,例如以上5条select语句彻底能够经过如下1条select语句来完成:

select * from CUSTOMERS left outer join ORDERS 
on CUSTOMERS.ID=ORDERS.CUSTOMER_ID 

以上select语句使用了SQL的左外链接查询功能,可以在一条select语句中查询出CUSTOMERS表的全部记录,以及匹配的ORDERS表的记录。

(2)在应用逻辑只须要访问Customer对象,而不须要访问Order对象的场合,加载Order对象彻底是多余的操做,这些多余的Order对象白白浪费了许多内存空间。
为了解决以上问题,Hibernate提供了其余两种检索策略:延迟检索策略和迫切左外链接检索策略。延迟检索策略能避免多余加载应用程序不须要访问的关联对象,迫切左外链接检索策略则充分利用了SQL的外链接查询功能,可以减小select语句的数目。缓存

Hibernate检索策略session

链接查询:
    关系型数据库之因此强大,其中一个缘由就是能够统一使用表来管理同类数据信息,而且能够在相关数据之间创建关系。做为支持关系型数据库的SQL语句来讲,天然要对全面发挥这种强大功能提供支持,这个支持就是链接查询。一样做为一种关系型数据库的持久层框架,Hibernate也对链接查询提供了丰富的支持,在Hibernate中经过HQLQBC两种查询方式均可以支持链接查询。下面这一部分咱们将经过这两种查询技术,来详细讨论有关Hibernate对链接查询支持的各个细节。在讲解链接查询以前,咱们先来回忆一下在第一部分中讲解的有关实体关联关系的映射,在实体的配置文件中能够经过配置集合元素来指定对关联实体的映射以及检索策略。(请参考第一部分相关内容)所以咱们能够在实体映射配置文件中,指定关联实体检索策略,对关联实体的检索策略能够指定为延迟检索当即检索迫切左外链接检索,以下所示对与Customer实体关联的Order实体设置延迟加载:<set name=”orders” inverse=”true” lazy=”true”>,这种在实体映射配置文件中设定的检索策略,称为默认检索策略,可是这种默认检索策略是能够被覆盖的,那就是在程序代码当中能够动态指定各类迫切检索策略来覆盖默认检索策略。
1     迫切左外链接查询和左外链接查询:
咱们看如下代码,这段代码将覆盖映射文件中的检索策略,显示指定采用迫切左外链接查询。

HQL查询方式:
Query query=session.createQuery(“from Customer c left join fetch c.orders o where c.name        like ‘zhao%’ ”);
List list=query.list();
for(int i=0;i<list.size();i++){
Customer customer=(Customer)list.get(i);
}
//QBC检索方式:
List list=session.createCriteria(Customer.class).setFetchMode(“orders”,FetchMode.EAGER)
         .add(Expression.like(“name”,”zhao%”,MatchMode.START).list();
for(int i=0;i<list.size();i++){
Customer customer=(Customer)list.get(i);
}
咱们看到在HQL以及QBC查询中分别经过left join fetchFetchMode.EAGER来指定采用迫切左外链接检索策略,当采用了迫切左外链接检索策略时,当进行检索时即执行查询的list()方法时,将会当即初始化用来容纳关联实体的集合对象元素,若是在实体映射配置文件中对关联实体设置了延迟加载,那么此时将会忽略延迟加载设置,而采用迫切左外链接策略,而且当即用关联实体对象填充集合对象元素,即便用Order对象填充Customer对象的orders集合。所以这种检索策略会立刻建立关联实体对象,此时我想你必定会想到这种检索策略会同时检索出CustomerOrder实体对象对应的数据,而且分别建立这两个对象。恭喜你答对了,所以上面代码会生成相似以下的SQL语句:
Select * from customer c left join order o on c.id=o.id where c.name like ‘zhao%’;
若是咱们忽略了fetch关键字,就变成了左外链接查询,以下面代码:
Query query=session.createQuery(“from Customer c left join c.orders o where c.name        like ‘zhao%’ ”);
List list=query.list();
for(int i=0;i<list.size();i++){
Object[] objs=(Object[])list.get(i);
Customer customer=(Customer) objs[0];
order order=(Order)objs[1];
}
咱们能够看到采用左外链接查询返回的结果集中包含的是对象数组,对象数组中的每一个元素存放了一对相互关联的Customer对象和Order对象,而迫切左外链接会返回Customer对象,与Customer对象相关联的Order对象存放在Customer对象的集合元素对象中,这就是迫切左外链接和左外链接查询的其中一个区别(这两种检索生成的SQL语句是同样的),另外一个区别是当使用左外链接时,对关联对象的检索会依照实体映射配置文件所指定的策略,而不会像迫切左外链接那样忽略它,好比此时对Customer对象关联的Order对象采用延迟加载,那么左外链接检索也会使用延迟加载机制检索Order对象。
2、内链接,迫切内链接以及隐式内链接:
若采用迫切内链接经过一下代码能够实现:
Query query=session.createQuery(“from Customer c inner join fetch c.orders o where c.name        like ‘zhao%’ ”);
List list=query.list();
for(int i=0;i<list.size();i++){
Customer customer=(Customer)list.get(i);
}
这段代码将会采用迫切内链接检索,对集合元素的检索策略以及返回结果集中的对象类型都采用与迫切左外链接同样的方式,我这里就再也不赘述,另外QBC查询不支持迫切内链接检索。
若是去掉fetch就是内链接检索,以下面代码:
Query query=session.createQuery(“from Customer c innerjoin c.orders o where c.name        like ‘zhao%’ ”);
List list=query.list();
for(int i=0;i<list.size();i++){
Object[] objs=(Object[])list.get(i);
Customer customer=(Customer) objs[0];
order order=(Order)objs[1];
}
内链接检索,对集合元素的检索策略以及返回结果集中的对象类型都采用与左外链接同样的方式,QBC查询也一样支持内链接检索,以下代码:
List list=session.createCriteria(Customer.class)
.add(Expression.like(“name”,”zhao%”,MatchMode.START))
.createCriteria(“orders”)
.add(Expression.like(“ordernumber”,”T”,MatchMode.START)).list();
       上面代码等价于以下的HQL语句:
       Select c from Customer c join c.orders o where c.name like ‘zhao%’ and o.ordernummber like ‘T%’;所以能够采用下面的方式访问结果集:
for(int i=0;i<list.size();i++){
Customer customer=(Customer)list.get(i);
}
       因而可知,采用内链接查询时,HQLQBC查询有不一样的默认行为,HQL会检索出成对的CustomerOrder对象,而QBC仅会检索出Customer对象。若是QBC查询想检索出成对的CustomerOrder对象,能够采用以下代码:
List list=session.createCriteria(Customer.class)
.createAlias(“orders”,”o”)
.add(Expression.like(“this.name”,”zhao%”,MatchMode.START))
.add(Expression.like(“ordernumber”,”T”,MatchMode.START))
.returnMap()
.list();
for(int i=0;i<list.size();i++){
    Map map=(Map)list.get(i);
Customer customer=(Customer)map.get(“this”);
order order=(Order)map.get(“o”);
}
     “o””this”分别是orders集合和Customer对象的别名。
     HQL查询中,还有一种查询成为隐式内链接,咱们看下面的HQL语句,
       From order o where o.customer.name like ’ zhao% ’;这个语句经过o.customer.name访问与Order对象关联的Customer对象的name属性,尽管没有使用join关键字,其实隐式指定了采用内链接检索,它和下面这条HQL语句等价:
From order o join o.customer c where c.name like ‘zhao%’;
隐式内链接只适用于多对一和一对一关联,不适用于一对多和多对多关联,另外QBC查询不支持隐式内链接检索。
3、右外链接检索:
因为fetch关键字只能应用于innner joinleft join,所以对于右外链接检索而言,就不存在所谓的迫切右外链接查询了,使用右外链接见以下代码:
Query query=session.createQuery(“from Customer c right join c.orders o where c.name        like ‘zhao%’ ”);
List list=query.list();
for(int i=0;i<list.size();i++){
Object[] objs=(Object[])list.get(i);
Customer customer=(Customer) objs[0];
order order=(Order)objs[1];
}
       右外链接检索,对集合元素的检索策略以及返回结果集中的对象类型都采用与左外链接同样的方式。
4、交叉链接:
对于不存在关联关系的两个实体对象,不能使用内链接查询,也不能使用外链接查询,此时可使用具备SQL风格的交叉链接,以下面代码:
Select c.ID,c.name,c.age,o.ID,o.ordernumber,o.customer_ID
From Customer c,Order o;
这个HQL语句将会执行交叉链接检索,并且将会返回customer表和order表的笛卡儿积关联结果。
5、链接查询运行时检索策略总结:
、若是在HQLQBC查询中没有指定检索策略,那么将会使用映射配置为件中指定的检索策略,可是这里有一个例外,那就是HQL检索老是会忽略实体映射配置文件中对关联实体指定的迫切左外链接检索策略,也就是说若是配置文件中指定对关联实体采用迫切走外链接检索,可是在HQL查询语句中没有指定这种检索策略,此时Hibernate将会忽略这种检索策略,而依然采用当即检索。所以若是但愿采用迫切左外链接检索,就必须在HQL语句中明确指定。
、若是在HQL或者QBC检索中明确指定了检索策略,就会覆盖配置文件中的默认检索策略,在HQL查询中经过left join fetchinner join fetch来明确指定检索策略,在QBC查询中经过FetchMode.DEFAULT,FetchMode.EAGER,FetchMode.LAZY来明确指定检索策略。
              目前的Hibernate的各类版本中,只容许在一个查询中迫切左外链接检索一个集合,即只容许存在一个一对多关联,可是容许存在多个一对一和多对多关联。框架

相关文章
相关标签/搜索