Hibernate one-to-one 的N+1问题分析及其解决方法

本文探讨了一对一双表查询中出现的N+1问题,并提供了两种解决方案:一是调整表结构,二是利用Hibernate公式。这两种方法都能有效减少数据库查询次数,提升应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


什么是N+1问题呢?



我们举一个例子来说明这个问题。
还是以 husband vs wife(夫:妻) 模型为例。
他们是一对一(one-to-one)的关系。并假设HUSBAND表为主表,WIFE表为从表。

CreateTables.sql

[java] view plain copy
  1. #
  2. #husbandTABLE
  3. #
  4. CREATETABLEHUSBAND(
  5. IDCHAR(32)NOTNULL,
  6. NAMECHAR(32)NOTNULL,
  7. PRIMARYKEY(id)
  8. )
  9. #
  10. #wifeTABLE
  11. #
  12. CREATETABLEWIFE(
  13. IDCHAR(32)NOTNULL,
  14. NAMECHAR(32)NOTNULL,
  15. HUSBAND_IDCHAR(32)NOTNULL,
  16. PRIMARYKEY(id),
  17. FOREIGNKEY(HUSBAND_ID)REFERENCEHUSBAND(ID)
  18. )


WIFE.HUSBAND_ID是一个外键,引用HUSBAND.ID。

假设在
Husband.hbm.xml里
定义与Wife的one-to-one关系。如下:
[java] view plain copy
  1. <one-to-one
  2. name="wife"
  3. cascade="all"
  4. class="mypackage.Wife"
  5. property-ref="husbandId">
  6. </one-to-one>

如果我们使用HQL或Criteria查询时,例如:

FROM Husband h WHERE h.name = :name

或与其他表作联结查询时,比如:
FROM Husband h, Family f WHERE h.name = :name AND f.hid = h.id

之类的查询时,如果查询出的结果中存在N条数据,我们通过查看Hibernate打印出来的SQL语句,会发现Hibernate共向数据库发送了N+1条SQL查询。

为什么有这个问题呢?由于Husband表中未定义与Wife的关系(关系),所以Hibernate在查询时,Husband并不知道与它对应的Wife信息,所以Hibernate只有把Husband查询出来之后,再调用一次类似以下的查询,
SELECT * FROM WIFE WHERE HUSBAND_ID=?
把Wife信息查询出来,然后设置到Husband对象中加以关联。

所以如果存在N条数据,Hibernate会对每一条数据调用上面的SQL查询。从而一共会查询N+1次。

这就是one-to-one 的N+1问题。N+1问题造成大量SQL语句的执行,往往会引起严重的性能问题。

为了解决one-to-one 的N+1问题,笔者作了大量的测试,也查询了很多资料。
其中的测试包括使用fetch join等很多资料加以介绍的,结果one-to-one的情况,fetch join并不起效;另外,也尝试了
<one-to-one fetch="join" constrained="true" lazy="false" .../>
的定义形式,同样不行。


one-to-one 的N+1问题的解决方法


方法一:重新定义表结构,把关联字段放在主表一方。

然后在主表一方用many-to-one关联从表。

这种方法比较直观,也是最容易想到的方法。如下:
表HUSBAND定义:

[java] view plain copy
  1. CREATETABLEHUSBAND(
  2. IDCHAR(32)NOTNULL,
  3. NAMECHAR(32)NOTNULL,
  4. WIFE_IDCHAR(32)NOTNULL,
  5. PRIMARYKEY(id),
  6. FOREIGNKEY(WIFE_ID)REFERENCEWIFE(ID)
  7. )

Husband.hbm.xml
[java] view plain copy
  1. <many-to-one
  2. name="wife"column="WIFE_ID"
  3. not-null="false"
  4. cascade="all"
  5. class="mypackage.Wife">
  6. </many-to-one>

方法二:表的结构定义不变。使用hibernate formula方程式。
方法如下:
Husband.hbm.xml
[java] view plain copy
  1. <many-to-one
  2. name="wife"
  3. not-null="false"
  4. cascade="all"
  5. class="mypackage.Wife">
  6. <formula>(selectw.IDfromWIFEwwherew.HUSBAND_ID=ID)</formula>
  7. </many-to-one>


解说:
<formula>(select w.ID from WIFE w where w.HUSBAND_ID = ID)</formula>
该formula告诉HIBERNATE怎么与让Husband与Wife关联。

这样查询时,只会向数据库发送一条数据。
但是,如果需要从Husband对象存取Wife对象 [调用Husband.getWife()方法] 时:
List <Husband> list = query.list();
list.get(0).getWife();

Hibernate会动态向数据库发送一条查询数据。所以第2种方法,若要在页面上同时显示多条主表Husband与从表Wife的混合数据时,显然也会发生N+1问题。
但若只是显示多条Husband数据时,只发送一条SQL语句。

什么是N+1问题呢?



我们举一个例子来说明这个问题。
还是以 husband vs wife(夫:妻) 模型为例。
他们是一对一(one-to-one)的关系。并假设HUSBAND表为主表,WIFE表为从表。

CreateTables.sql

[java] view plain copy
  1. #
  2. #husbandTABLE
  3. #
  4. CREATETABLEHUSBAND(
  5. IDCHAR(32)NOTNULL,
  6. NAMECHAR(32)NOTNULL,
  7. PRIMARYKEY(id)
  8. )
  9. #
  10. #wifeTABLE
  11. #
  12. CREATETABLEWIFE(
  13. IDCHAR(32)NOTNULL,
  14. NAMECHAR(32)NOTNULL,
  15. HUSBAND_IDCHAR(32)NOTNULL,
  16. PRIMARYKEY(id),
  17. FOREIGNKEY(HUSBAND_ID)REFERENCEHUSBAND(ID)
  18. )


WIFE.HUSBAND_ID是一个外键,引用HUSBAND.ID。

假设在
Husband.hbm.xml里
定义与Wife的one-to-one关系。如下:
[java] view plain copy
  1. <one-to-one
  2. name="wife"
  3. cascade="all"
  4. class="mypackage.Wife"
  5. property-ref="husbandId">
  6. </one-to-one>

如果我们使用HQL或Criteria查询时,例如:

FROM Husband h WHERE h.name = :name

或与其他表作联结查询时,比如:
FROM Husband h, Family f WHERE h.name = :name AND f.hid = h.id

之类的查询时,如果查询出的结果中存在N条数据,我们通过查看Hibernate打印出来的SQL语句,会发现Hibernate共向数据库发送了N+1条SQL查询。

为什么有这个问题呢?由于Husband表中未定义与Wife的关系(关系),所以Hibernate在查询时,Husband并不知道与它对应的Wife信息,所以Hibernate只有把Husband查询出来之后,再调用一次类似以下的查询,
SELECT * FROM WIFE WHERE HUSBAND_ID=?
把Wife信息查询出来,然后设置到Husband对象中加以关联。

所以如果存在N条数据,Hibernate会对每一条数据调用上面的SQL查询。从而一共会查询N+1次。

这就是one-to-one 的N+1问题。N+1问题造成大量SQL语句的执行,往往会引起严重的性能问题。

为了解决one-to-one 的N+1问题,笔者作了大量的测试,也查询了很多资料。
其中的测试包括使用fetch join等很多资料加以介绍的,结果one-to-one的情况,fetch join并不起效;另外,也尝试了
<one-to-one fetch="join" constrained="true" lazy="false" .../>
的定义形式,同样不行。


one-to-one 的N+1问题的解决方法


方法一:重新定义表结构,把关联字段放在主表一方。

然后在主表一方用many-to-one关联从表。

这种方法比较直观,也是最容易想到的方法。如下:
表HUSBAND定义:

[java] view plain copy
  1. CREATETABLEHUSBAND(
  2. IDCHAR(32)NOTNULL,
  3. NAMECHAR(32)NOTNULL,
  4. WIFE_IDCHAR(32)NOTNULL,
  5. PRIMARYKEY(id),
  6. FOREIGNKEY(WIFE_ID)REFERENCEWIFE(ID)
  7. )

Husband.hbm.xml
[java] view plain copy
  1. <many-to-one
  2. name="wife"column="WIFE_ID"
  3. not-null="false"
  4. cascade="all"
  5. class="mypackage.Wife">
  6. </many-to-one>

方法二:表的结构定义不变。使用hibernate formula方程式。
方法如下:
Husband.hbm.xml
[java] view plain copy
  1. <many-to-one
  2. name="wife"
  3. not-null="false"
  4. cascade="all"
  5. class="mypackage.Wife">
  6. <formula>(selectw.IDfromWIFEwwherew.HUSBAND_ID=ID)</formula>
  7. </many-to-one>


解说:
<formula>(select w.ID from WIFE w where w.HUSBAND_ID = ID)</formula>
该formula告诉HIBERNATE怎么与让Husband与Wife关联。

这样查询时,只会向数据库发送一条数据。
但是,如果需要从Husband对象存取Wife对象 [调用Husband.getWife()方法] 时:
List <Husband> list = query.list();
list.get(0).getWife();

Hibernate会动态向数据库发送一条查询数据。所以第2种方法,若要在页面上同时显示多条主表Husband与从表Wife的混合数据时,显然也会发生N+1问题。
但若只是显示多条Husband数据时,只发送一条SQL语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值