Hibernate FetchMode.JOIN工作原理及一对多关联N+1查询问题排查
Hibernate FetchMode.JOIN 原理与N+1查询问题解决
先给你理清楚FetchMode.JOIN的核心逻辑,再看你的代码哪里出了问题,最后给出修复方案。
FetchMode.JOIN 到底怎么工作?
FetchMode.JOIN是Hibernate用来强制立即加载关联实体的查询策略,它的核心作用是:在查询主实体(比如这里的Vendor)时,自动通过SQL的JOIN语句把关联的子实体(Customer)数据一次性拉取回来,从根源上避免讨厌的N+1查询问题。
不过要注意几个关键点:
- 它是绑定到当前Criteria查询的临时策略,不会改变实体映射文件里配置的默认加载规则(比如懒加载)。
- 必须保证你在Criteria里指定的关联属性名,和映射文件里定义的关联名称完全匹配,否则这个配置直接失效。
你的代码为什么会触发8条查询?
看了你的映射文件和代码,问题其实很直观:
- 关联属性名称写错了!你在Criteria里写的是
criteria.setFetchMode("customer", FetchMode.JOIN);,但你的Vendor映射文件里,关联Customer的集合名称是children(<set name="children">),根本不是customer。Hibernate找不到对应的关联属性,等于你白设置了FetchMode,还是走了默认的懒加载逻辑。 - 你的
<set>标签没有显式配置加载策略,默认是lazy="true",所以当你遍历vendor.getChildren()时,Hibernate会为每个Vendor单独发一条查询去拉Customer数据,这就导致了1条Vendor查询+8条Customer查询的N+1问题。
怎么修复这个问题?
1. 修正FetchMode的关联属性名
这是最直接的修复,把Criteria里的关联名改成映射文件里的children:
Criteria criteria = session.createCriteria(Vendor.class); // 把"customer"改成"children",和映射文件中的set名称一致 criteria.setFetchMode("children", FetchMode.JOIN); List<Vendor> listOfVendore = criteria.list(); Iterator<Vendor> veIterator = listOfVendore.iterator(); while (veIterator.hasNext()) { Vendor vendor = veIterator.next(); Set<Customer> listCustomer = vendor.getChildren(); // 这里遍历的时候已经有数据了,不会再触发新查询 Iterator<Customer> iterator = listCustomer.iterator(); while (iterator.hasNext()) { Customer customer = iterator.next(); } }
修正后,Hibernate会自动生成带LEFT JOIN的SQL,一次性查询所有Vendor和对应的Customer数据,不会再发额外的查询。
2. (可选)全局配置关联的加载策略
如果你希望所有查询Vendor的操作都默认加载Customer,可以修改Vendor的映射文件,给<set>标签加上fetch="join":
<set name="children" cascade="all" inverse="true" fetch="join"> <key column="vendid" /> <one-to-many class="str.Customer" /> </set>
不过这种方式灵活性差一点,所有查询都会自动JOIN加载Customer,不如在Criteria中动态设置FetchMode灵活。
3. 验证修复后的SQL
修复成功后,Hibernate会生成类似这样的SQL:
select this_.vendid as vendid1_1_0_, this_.vendname as vendname2_1_0_, children0_.vendid as vendid3_1_1_, children0_.custid as custid1_0_1_, children0_.custid as custid1_0_0_, children0_.custname as custname2_0_0_, children0_.vendid as vendid3_0_0_ from vendor this_ left outer join customer children0_ on this_.vendid=children0_.vendid
只会有这一条SQL,彻底解决N+1问题。
内容的提问来源于stack exchange,提问作者madhusudhan
相关产品推荐
相关产品推荐

