n+1次select查询问题

方法一:

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



以下Sessionfind()方法用于到数据库中检索所有的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;

通过以上5select语句,Hibernate最后加载了4Customer对象和5Order对象,在内存中形成了一幅关联的对象图,参见图2



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

1 select语句的数目太多,需要频繁的访问数据库,会影响检索性能。如果需要查询nCustomer对象,那么必须执行n+1select查询语句。这就是经典的n+1select查询问题。这种检索策略没有利用SQL的连接查询功能,例如以上5select语句完全可以通过以下1select语句来完成:

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 in Action中已经有很多种解决办法了。但我觉得其中最好的办法是用CriteriaFetchMode来解决,但是Hibernate in Action中写的很不详细。我昨晚试了好长时间来的到答案。下面总结一下。
需求这样的,我有四张表(one,two,three,four)从one一直外键关联到four。结构如下

现在在Session中得到One,并从One里一直取到Four里的内容。如果简单的用Session.get来实现是这样的。

One one = (One)session.get(One.class,new Integer(1));
        Iterator iterone = one.getTwos().iterator();
        
while(iterone.hasNext()){
            Two two = (Two) iterone.next();
            Iterator itertwo = two.getThrees().iterator();
            
while(itertwo.hasNext()){
                Three three = (Three) itertwo.next();
                three.getFours().size();                
            }
        }

这样我在Session关闭后返回的One里是从OneFour的信息都有的。
然而这样做所导致的结果是生成大量的SQL查询,这是一个典型的n+1 Selects问题。如果系统结构层次多,符合条件的记录多,那么Hibernate为你生成的SQL查询将是难以接受的。
对于这个例子生成的SQL是这样的
Hibernate: select one0_.c_one_id as c1_0_, one0_.c_one_text as c2_3_0_ from One one0_ where one0_.c_one_id=?
Hibernate: select twos0_.c_one_id as c2_1_, twos0_.c_two_id as c1_1_, twos0_.c_two_id as c1_0_, twos0_.c_one_id as c2_2_0_, twos0_.c_two_text as c3_2_0_ from Two twos0_ where twos0_.c_one_id=?
Hibernate: select threes0_.c_two_id as c2_1_, threes0_.c_three_id as c1_1_, threes0_.c_three_id as c1_0_, threes0_.c_two_id as c2_1_0_, threes0_.c_three_text as c3_1_0_ from Three threes0_ where threes0_.c_two_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select threes0_.c_two_id as c2_1_, threes0_.c_three_id as c1_1_, threes0_.c_three_id as c1_0_, threes0_.c_two_id as c2_1_0_, threes0_.c_three_text as c3_1_0_ from Three threes0_ where threes0_.c_two_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
Hibernate: select fours0_.c_three_id as c2_1_, fours0_.c_four_id as c1_1_, fours0_.c_four_id as c1_0_, fours0_.c_three_id as c2_0_0_, fours0_.c_four_text as c3_0_0_ from Four fours0_ where fours0_.c_three_id=?
对于这样的问题,在没有Hibernate以前我们一般都用jdbc来做,那样的话我们其实用一个进行3joinsql语句就可以实现,但是这样解决也有问题,就是返回的ResultSet中的数据非常多,而且杂乱,其实是从onefour平行排列的。对于这样的结果集我们要把它手动影射曾对象结构也是一个很复杂的操作。
幸好Hibernate3可以为我们做这些事情(我再一次被Hibernate的强大所震撼)
上面的实现可以用Criteria来实现:

session = sessionFactory.openSession();
        Criteria criteria = session.createCriteria(One.
class);
        criteria.add(Expression.eq("COneId",
new Integer(1)));
        one = (One)criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN).setFetchMode("twos.threes.fours",FetchMode.JOIN).uniqueResult();
        session.close();

这里的重点是这句话criteria.setFetchMode("twos",FetchMode.JOIN).setFetchMode("twos.threes",FetchMode.JOIN).setFetchMode("twos.threes.fours",FetchMode.JOIN).uniqueResult();
在用Criteria之前先设置FetchMode,应为Criteria是动态生成sql语句的,所以生成的sql就是一层层Join下去的。

setFetchMode
StringMode)第一个参数是association path,用"."来表示路径。这一点具体的例子很少,文档也没有写清楚。我也是试了很久才试出来的。
就这个例子来所把因为取道第四层,所以要进行三次setFetchMode
第一次的路径是twos,一位one中有twoSet。这个具体要更具hbm.xml的配置来定。

第二个路径就是twos.threes
第三个就是
twos.threes.fours
一次类推,一层层增加的。

这样做法最终生成的SQL是这样的:
Hibernate: select this_.c_one_id as c1_3_, this_.c_one_text as c2_3_3_, twos2_.c_one_id as c2_5_, twos2_.c_two_id as c1_5_, twos2_.c_two_id as c1_0_, twos2_.c_one_id as c2_2_0_, twos2_.c_two_text as c3_2_0_, threes3_.c_two_id as c2_6_, threes3_.c_three_id as c1_6_, threes3_.c_three_id as c1_1_, threes3_.c_two_id as c2_1_1_, threes3_.c_three_text as c3_1_1_, fours4_.c_three_id as c2_7_, fours4_.c_four_id as c1_7_, fours4_.c_four_id as c1_2_, fours4_.c_three_id as c2_0_2_, fours4_.c_four_text as c3_0_2_ from One this_ left outer join Two twos2_ on this_.c_one_id=twos2_.c_one_id left outer join Three threes3_ on twos2_.c_two_id=threes3_.c_two_id left outer join Four fours4_ on threes3_.c_three_id=fours4_.c_three_id where this_.c_one_id=?
虽然很长但是只有一条SQL语句。性能要好很多。Hibernate的强大之处是它会把返回的ResultSet自动影射到你的对象模型里面去。这就为我们省了很多事。

看来Hibernate真是一个耐人寻味的Framework啊。

"from A j left outer join j.Bs a where  ......

可以直接进行左连接:一对多的关系。后面不能再写on 。。。这个句子会自动生成sql,其中会有的。

" and a.id is not null");   判断不为空。

方法二:

还有一种解决方法:就是直接写成一个视图用来作查询,这样外连接都已经写好了。

方法三:

对于双向的一对多的关系lazy

一方:<set name="processVOs"

        inverse="true"

        cascade="all-delete-orphan"

        lazy="true"

        >

多方:<class
   
name="ProcessVO"
   
table="TABLE_BPROCESS "
   
lazy="true"   //一定要为true

>
前且在对应的VO中不能有set.sixe()等大小。这样因为延时加载,如果取不出数据所以会发生错误。应该改为lazy=”false”

如果有这样的情况:处理方法1.采用视图;2。采用左连接 (也就是上面的方法一) 如: select id, count(f_id) from A left outer join B  where ……   group by id  order by…..

                                                             

 

 

 

 

 

 

 

 

 

 

 

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值