`

hiBernate jpa N+1问题

 
阅读更多

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

选自<<精通Hibernate:Java对象持久化技术详解>> 作者:孙卫琴 来源:www.javathinker.org
如果转载,请标明出处,谢谢


在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语句的数目。

==============================================

1、outer-join关键字(many-to-one的情况)

outer-join关键字有3个值,分别是true,false,auto,默认是auto。
true: 表示使用外连接抓取关联的内容,这里的意思是当使用load(OrderLineItem.class,"id")时,Hibernate只生成一条SQL语句将OrderLineItem与他的父亲Order全部初始化。

select * from OrderLineItem o left join Order p on o.OrderId=p.OrderId  where o.OrderLineItem_Id=?

false:表示不使用外连接抓取关联的内容,当load(OrderLineItem.class,"id")时,Hibernate生成两条SQL语句,一条查询OrderLineItem表,另一条查询Order表。这样的好处是可以设置延迟加载,此处要将Order类设置为lazy=true。

select * from OrderLineItem o where o.OrderLineItem_Id=?
select * from Order p where p.OrderId=?

auto:具体是ture还是false看hibernate.cfg.xml中的配置

注意:如果使用HQL查询OrderLineItem,如 from OrderLineItem o where o.id='id',总是不使用外部抓取,及outer-join失效。

2、outer-join(集合)

由于集合可以设置lazy="true",所以lazy与outer-join不能同时为true,当lazy="true"时,outer-join将一直是false,如果lazy="false",则outer-join用法与1同

3、HQL语句会将POJO配置文件中的关联一并查询,即使在HQL语句中没有明确join

4、In HQL, the "fetch join" clause can be used for per-query specific outer join fetching. One important thing many people miss there, is that HQL queries will ignore the outer-join attribute you specified in your mapping. This makes it possible to configure the default loading behaviour of session.load() and session.get() and of objects loaded by navigating relationship. So if you specify

and then do
MyObject obj = session.createQuery("from MyObject").uniqueResult();
obj.getMySet().iterator().next();

you will still have an additional query and no outer-join. So you must explicily request the outer-join fetching:

MyObject obj = session.createQuery(
    "from MyObject mo left join fetch mo.mySet").uniqueResult();
obj.getMySet().iterator().next();

Another important thing to know is that you can only fetch one collection reference in a query. That means you can just use one fetch join. You can however fetch "one" references in addition, as this sample from the Hibernate Docs demonstrates:

from eg.Cat as cat
    inner join fetch cat.mate
    left join fetch cat.kittens

We have once considered lifting this limitation, but then decided against it, because using more than one fetch-join would be a bad idea generally: The generated ResultSet becomes huge and is a major performance loss.

So alltogether the "fetch join" clause is an important instrument Hibernate users should learn how to leverage, as it allows tuning the fetch behaviour of a certain use case.

5、join fetchjoin 的区别

如果HQL使用了连接,但是没有使用fetch关键字,则生成的SQL语句虽然有连接,但是并没有取连接表的数据,还是需要单独的sql取数据,也就是 select a,b,d...中没有连接表的字段

6、如果集合被声明为lazy=true,在HQL中如果显式的使用 join fetch 则延迟加载失效。

7、在one-to-many的one端显式设置fecth="join",则无论如何都采取预先抓取(生成一个SQl),延迟加载失效(生成两个SQL)

8、many-to-one的延迟加载是在配置文件的class标签设置lazy="true",one-to-many和many-to-many的延迟加载是在set标签中设置lazy="true"。而one-to-one不只要在calss标签设置lazy="true",而且要在one-to-one标签中设置constrained="true".

===============================

实这个问题在Hibernate in Action中已经有很多种解决办法了。但我觉得其中最好的办法是用Criteria的FetchMode来解决,但是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里是从One到Four的信息都有的。
然而这样做所导致的结果是生成大量的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来做,那样的话我们其实用一个进行3次join的sql语句就可以实现,但是这样解决也有问题,就是返回的ResultSet中的数据非常多,而且杂乱,其实是从one到four平行排列的。对于这样的结果集我们要把它手动影射曾对象结构也是一个很复杂的操作。
幸好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(String,Mode)第一个参数是association path,用"."来表示路径。这一点具体的例子很少,文档也没有写清楚。我也是试了很久才试出来的。
就这个例子来所把因为取道第四层,所以要进行三次setFetchMode
第一次的路径是twos,一位one中有two的Set。这个具体要更具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自动影射到你的对象模型里面去。这就为我们省了很多事。

  • 大小: 5.1 KB
  • 大小: 4.5 KB
  • 大小: 29.9 KB
分享到:
评论

相关推荐

    hibernate N+1问题解决办法

    **标题:“Hibernate N+1问题解决办法”** 在Java开发中,使用Hibernate作为ORM框架时,我们可能会遇到一个性能上的问题,那就是著名的“N+1查询问题”。此问题源于不恰当的数据加载策略,可能导致数据库查询效率...

    How to solve the hibernate N+1 problem?

    标题 "如何解决Hibernate的N+1问题" 涉及的是一个常见的数据库查询优化问题,主要出现在使用Hibernate等ORM框架时。N+1问题是指在进行一对多或多对多关联查询时,原本期望通过一次SQL查询获取所有数据,但实际执行了...

    用于在基于JPA的Spring Boot Java应用程序中自动检测和断言“N+1选择问题”发生的工具,并在一般情况下发现JPA发出的SQL语句的来源- adgadev/jplusone

    它通过跟踪JPA执行的SQL语句,分析其执行模式,来找出可能存在的N+1问题。这有助于提升应用程序的性能,特别是在微服务架构中,性能优化是至关重要的。 1. **N+1选择问题**:当我们在查询主数据的同时,没有一次性...

    hibernate-jpa.jar

    然而,需要注意的是,虽然ORM工具带来了便利,但也可能因为过度依赖导致性能问题,如N+1查询、懒加载地狱等,因此在实际使用中需权衡利弊,合理设计数据访问策略。 总的来说,Hibernate JPA.jar文件是Java开发者...

    hibernate-jpa

    - 避免过多的N+1查询问题,合理设计关联关系。 - 使用预编译查询(NamedQuery或Criteria API)提高性能。 - 注意实体的生命周期管理,避免内存泄漏。 总结来说,“Hibernate-JPA”是Java开发中一个强大且易用的数据...

    hibernate-jpa-2.1-api 1.0.0.Final API

    13. **实体图形(Entity Graphs)**:在查询时控制哪些关联应被加载,有助于避免N+1查询问题。 以上知识点构成了Hibernate JPA 2.1 API的核心内容,开发者可以通过提供的离线文档深入理解和使用这些功能。

    jpa_hibernate项目 所有jar包

    8. **性能优化**:掌握查询优化技巧,如避免N+1查询问题,使用批处理等。 **实践应用**: 1. 创建一个简单的JPA_Hibernate项目,从零开始设置配置,创建实体,编写DAO层操作。 2. 实现用户注册和登录功能,涉及数据...

    Hibernate_JPA

    在实际应用中,应注意避免N+1查询问题,合理设计实体关系,控制事务粒度,以及充分利用Hibernate的缓存机制等,以实现高效、稳定的持久化操作。 总结,Hibernate与JPA的结合使用,为Java开发者提供了强大的持久化...

    struts+hibernate+spring集成实现分页

    在实际项目中,还需要考虑性能优化,比如缓存策略、避免N+1查询问题,以及如何处理用户跳转到非法页码等情况。此外,还可以使用Spring Data JPA等更高级的库来简化分页实现,提高开发效率。 通过SSH集成实现分页,...

    hibernate入门学习笔记+源码

    5. **查询优化**: 避免N+1查询问题,合理使用JOIN,减少数据库交互次数。 **六、源码分析** `hibernate_test`、`hibernate_test3`、`hibernate_test2`这些文件可能包含了示例项目的源代码,包括配置文件、实体类、...

    hibernate5.2.10+struts2.5项目开发jar包

    3. **更好的类型安全**:引入了EntityGraph,用于在实体级别定义加载策略,避免了潜在的N+1查询问题。 4. **JDBC 4.2支持**:利用新版本的JDBC API,提供更丰富的数据库交互能力。 5. **更好的事务管理**:支持...

    Hibernate入门(代码+笔记)

    以及开放 session 在视图(Open Session In View,OSIV)模式,确保在Web请求生命周期内保持一个持久化上下文,避免N+1查询问题。 总结来说,本教程涵盖了Hibernate的基础知识,从入门到实体映射,再到复合主键和...

    java springboot+jpa

    - 优化查询性能,避免 N+1 查询问题。 - 使用 Spring Data JPA 的 Pageable 接口进行分页查询,以提高效率。 通过这个"springboot-jpa"项目,你可以深入了解 Spring Boot 如何与 JPA 结合,实践各种 JPA 和 Spring ...

    Java+Persistence+with+Hibernate

    - 合理设计实体结构,避免N+1查询问题。 - 使用批处理操作提高性能。 - 注意事务粒度,平衡性能与数据一致性。 - 慎重使用JOIN,避免大数据量的笛卡尔积。 通过学习《Java+Persistence+with+Hibernate》,开发者...

    spring4.0整合hibernate5.1.0.Final+mysql5

    - 应用懒加载(lazy loading)以提高性能,但要注意N+1查询问题。 - 使用第一级缓存和第二级缓存提高数据访问效率。 - 对敏感操作进行事务管理,确保数据一致性。 综上所述,"Spring4.0整合hibernate5.1.0.Final...

    SSH框架整合spring4+hibernate4+struts2(终结版)

    此外,性能优化也是SSH项目中不可忽视的部分,如合理配置缓存、避免N+1查询等。 综上所述,SSH框架的整合是一项重要的技能,它涉及到了Java应用开发的多个层面,包括业务逻辑处理、数据持久化以及用户交互。理解并...

    spring+hibernate+struts页面

    同时,还需要注意优化性能,如合理使用缓存,避免N+1查询等,以提升系统整体性能。总之,Spring、Hibernate和Struts的结合使用,为Java Web开发提供了强大的工具集,使得开发者能够更加专注于业务逻辑,而非底层实现...

    Struts 2+Hibernate+Spring整合开发技术详解 12~17章

    - **Spring与Hibernate整合**:配置Spring的数据源、SessionFactory,使用Spring的HibernateTemplate或JPA实现数据访问。 - **整体架构**:理解三层架构(表现层、业务逻辑层、数据访问层)在整合后的组织方式,...

    struts2 + spring2.5 + hibernate3.0 + oracle 整合实例

    但同时,也需要注意性能优化,例如合理设计数据库索引,避免N+1查询问题,以及使用缓存技术提升应用性能。 总的来说,这个SSH整合实例涵盖了Java Web开发中的多个关键点,包括MVC设计模式、依赖注入、事务管理以及...

    hibernate所包含的所有jar包

    使用Hibernate可以显著提高开发效率,减少与数据库交互的代码量,但同时也需要注意性能优化,比如合理使用缓存、避免N+1查询等问题。总之,这个压缩包提供了一站式解决方案,让你能够快速开始使用Hibernate进行Java...

Global site tag (gtag.js) - Google Analytics