2009-11-02 87 views
0

鑑於以下休眠查詢選擇:休眠N + 1從在多個表

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 = ? 

相反,我看到很多很多的select語句射擊。

的數據模型這裏看起來是這樣的:

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

我想完整的對象圖SELECT語句在一個訪問數據庫,這就是爲什麼我明確地使用「返回讀取「在hibernate查詢中。

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對象中的父/子關係造成的。爲了這個選擇的目的,我實際上只需要獲取父對象。

有什麼建議嗎?

+0

什麼代碼看起來像是觸發懶加載?你在迭代什麼? – 2009-11-02 14:29:37

+0

您可以發佈您看到的「許多選擇語句」以及上述4個實體的完整映射嗎?所有這些選擇僅基於這4個表格嗎?這些實體是否還有其他「熱切」映射的關聯? – ChssPly76 2009-11-02 16:50:09

+0

@ ChssPly76 - 我已經添加了UML和休眠。如果這還不夠,請讓我知道。 – 2009-11-03 13:21:07

回答

0

除非您將集合映射爲延遲加載,否則當您抓取對象時,無論其他HQL如何,它都會生成多個選擇。將您的連接映射更改爲延遲加載。此外,除非connectionDetails永遠不能爲空,否則我建議您將最後一次連接更改爲左連接。

+0

我不明白爲什麼它會生成額外的數據庫命中? HQL明確聲明每個連接的「渴望」,所以它應該能夠使用單個選擇來檢索所有數據,不是嗎? – 2009-11-03 12:03:25

+0

我無法解釋它背後的機制。只是我的經驗是,不管HQL如何,如果你非拉拉地繪製一個集合,它會做N + 1選擇。 – Jherico 2009-11-03 17:25:42

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,你會發現它被應用到ALL列在所有表中返回,從而使它毫無意義。