从多个表中选择Hibernate N + 1

时间:2009-11-02 13:11:14

标签: java hibernate

给出以下hibernate查询:

String sql = "select distinct changeset " +
    "from Changeset changeset " +
    "join fetch changeset.changeEntries as changeEntry " +
    "join fetch changeEntry.repositoryEntity as repositoryEntity " +
    "join fetch repositoryEntity.project as project " +
    "join fetch changeset.author as changesetAuthor " +
    "where project.id = :projectID ";

为什么会导致N + 1问题?

我希望这会生成以下单个SQL语句(或类似的东西)

select *
  from Changeset 
  inner join changeEntry on changeset.id = changeEntry.changeset_id
  inner join repositoryEntity on changeEntry.repositoryentity_id = repositoryentity.id
  inner join project on repositoryentity.project_id = project.id
where project.id = ?

相反,我看到许多选择语句被触发。

此处的数据模型如下所示:

alt text http://img29.imageshack.us/img29/4123/uml.png

我想在一次访问数据库时从Select语句返回完整的对象图,这就是我在hibernate查询中明确使用“fetch”的原因。

Hibernate日志语句如下:

Hibernate: select distinct changeset0_.id as id2_0_, changeentr1_.id as id1_1_, repository2_.id as id9_2_, project3_.id as id6_3_, user4_.id as id7_4_, changeset0_.author_id as author5_2_0_, changeset0_.createDate as createDate2_0_, changeset0_.message as message2_0_, changeset0_.revision as revision2_0_, changeentr1_.changeType as changeType1_1_, changeentr1_.changeset_id as changeset4_1_1_, changeentr1_.diff as diff1_1_, changeentr1_.repositoryEntity_id as reposito5_1_1_, changeentr1_.repositoryEntityVersion_id as reposito6_1_1_, changeentr1_.sourceChangeEntry_id as sourceCh7_1_1_, changeentr1_.changeset_id as changeset4_0__, changeentr1_.id as id0__, repository2_.project_id as connecti6_9_2_, repository2_.name as name9_2_, repository2_.parent_id as parent7_9_2_, repository2_.path as path9_2_, repository2_.state as state9_2_, repository2_.type as type9_2_, project3_.projectName as connecti2_6_3_, project3_.driverName as driverName6_3_, project3_.isAnonymous as isAnonym4_6_3_, project3_.lastUpdatedRevision as lastUpda5_6_3_, project3_.password as password6_3_, project3_.url as url6_3_, project3_.username as username6_3_, user4_.username as username7_4_, user4_.email as email7_4_, user4_.name as name7_4_, user4_.password as password7_4_, user4_.principles as principles7_4_, user4_.userType as userType7_4_ from Changeset changeset0_ inner join ChangeEntry changeentr1_ on changeset0_.id=changeentr1_.changeset_id inner join RepositoryEntity repository2_ on changeentr1_.repositoryEntity_id=repository2_.id inner join project project3_ on repository2_.project_id=project3_.id inner join users user4_ on changeset0_.author_id=user4_.id where project3_.id=? order by changeset0_.revision desc
Hibernate: select repository0_.id as id10_9_, repository0_.changeEntry_id as changeEn2_10_9_, repository0_.repositoryEntity_id as reposito3_10_9_, changeentr1_.id as id1_0_, changeentr1_.changeType as changeType1_0_, changeentr1_.changeset_id as changeset4_1_0_, changeentr1_.diff as diff1_0_, changeentr1_.repositoryEntity_id as reposito5_1_0_, changeentr1_.repositoryEntityVersion_id as reposito6_1_0_, changeentr1_.sourceChangeEntry_id as sourceCh7_1_0_, changeset2_.id as id2_1_, changeset2_.author_id as author5_2_1_, changeset2_.createDate as createDate2_1_, changeset2_.message as message2_1_, changeset2_.revision as revision2_1_, user3_.id as id7_2_, user3_.username as username7_2_, user3_.email as email7_2_, user3_.name as name7_2_, user3_.password as password7_2_, user3_.principles as principles7_2_, user3_.userType as userType7_2_, repository4_.id as id9_3_, repository4_.project_id as connecti6_9_3_, repository4_.name as name9_3_, repository4_.parent_id as parent7_9_3_, repository4_.path as path9_3_, repository4_.state as state9_3_, repository4_.type as type9_3_, project5_.id as id6_4_, project5_.projectName as connecti2_6_4_, project5_.driverName as driverName6_4_, project5_.isAnonymous as isAnonym4_6_4_, project5_.lastUpdatedRevision as lastUpda5_6_4_, project5_.password as password6_4_, project5_.url as url6_4_, project5_.username as username6_4_, repository6_.id as id9_5_, repository6_.project_id as connecti6_9_5_, repository6_.name as name9_5_, repository6_.parent_id as parent7_9_5_, repository6_.path as path9_5_, repository6_.state as state9_5_, repository6_.type as type9_5_, repository7_.id as id10_6_, repository7_.changeEntry_id as changeEn2_10_6_, repository7_.repositoryEntity_id as reposito3_10_6_, repository8_.id as id9_7_, repository8_.project_id as connecti6_9_7_, repository8_.name as name9_7_, repository8_.parent_id as parent7_9_7_, repository8_.path as path9_7_, repository8_.state as state9_7_, repository8_.type as type9_7_, changeentr9_.id as id1_8_, changeentr9_.changeType as changeType1_8_, changeentr9_.changeset_id as changeset4_1_8_, changeentr9_.diff as diff1_8_, changeentr9_.repositoryEntity_id as reposito5_1_8_, changeentr9_.repositoryEntityVersion_id as reposito6_1_8_, changeentr9_.sourceChangeEntry_id as sourceCh7_1_8_ from RepositoryEntityVersion repository0_ left outer join ChangeEntry changeentr1_ on repository0_.changeEntry_id=changeentr1_.id left outer join Changeset changeset2_ on changeentr1_.changeset_id=changeset2_.id left outer join users user3_ on changeset2_.author_id=user3_.id left outer join RepositoryEntity repository4_ on changeentr1_.repositoryEntity_id=repository4_.id left outer join project project5_ on repository4_.project_id=project5_.id left outer join RepositoryEntity repository6_ on repository4_.parent_id=repository6_.id left outer join RepositoryEntityVersion repository7_ on changeentr1_.repositoryEntityVersion_id=repository7_.id left outer join RepositoryEntity repository8_ on repository7_.repositoryEntity_id=repository8_.id left outer join ChangeEntry changeentr9_ on changeentr1_.sourceChangeEntry_id=changeentr9_.id where repository0_.id=?

第二个重复多次 - 对于17个对象的结果集,第二个语句执行521次。

我怀疑这是RepositoryEntity对象中父/子关系的结果。出于这个选择的目的,我实际上只需要获取父对象。

有什么建议吗?

2 个答案:

答案 0 :(得分:0)

除非您将集合映射为延迟加载,否则当您获取对象时,无论其他HQL如何,它都会生成多个选择。将连接映射更改为延迟加载。另外,除非connectionDetails永远不能为空,否则我建议您将最后一个连接更改为左连接。

答案 1 :(得分:0)

您发布的第一个SQL是您期望的那个(不包括“预期”SQL中缺少的inner join users - 但它存在于您的HQL中,这是正确的。)

第二个SQL是(为简洁起见而简化):

select *
  from RepositoryEntityVersion repository0_
  left outer join ChangeEntry changeentr1_ on repository0_.changeEntry_id=changeentr1_.id
  left outer join Changeset changeset2_ on changeentr1_.changeset_id=changeset2_.id
  left outer join users user3_ on changeset2_.author_id=user3_.id
  left outer join RepositoryEntity repository4_ on changeentr1_.repositoryEntity_id=repository4_.id
  left outer join project project5_ on repository4_.project_id=project5_.id
  left outer join RepositoryEntity repository6_ on repository4_.parent_id=repository6_.id
  left outer join RepositoryEntityVersion repository7_ on changeentr1_.repositoryEntityVersion_id=repository7_.id
  left outer join RepositoryEntity repository8_ on repository7_.repositoryEntity_id=repository8_.id
  left outer join ChangeEntry changeentr9_ on changeentr1_.sourceChangeEntry_id=changeentr9_.id
 where repository0_.id=?

这里的基表是RepositoryEntityVersion,它不在你的图表上;我猜它在RepositoryEntity上被映射为一对多?我进一步猜测它被映射为急切的提取,这就是你的问题所在。

您需要将其映射为惰性或在join fetch的查询中明确提及它。然而,后者可能是不合需要的,因为可能涉及的数据量和(可能)返回重复的根实体实例。 distinct并不总是有帮助;看看你发布的SQL,你会看到它被应用于所有表格中返回的所有列,从而使它变得毫无意义。