2009-11-02 2 views
0

Учитывая следующий запрос спящего режима:Hibernate 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 = ? 

Вместо этого, я вижу много много операторов выбора стрельбы.

модель данных здесь выглядит следующим образом:

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

Я хотел бы полный объектный граф вернулся из Select заявление в одну поездку в базу данных, поэтому я явно с помощью " 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=? 

2-й один повторяется много раз - для результирующего набора из 17 объектов, то второе утверждение выполняется 521 раз.

Я подозреваю, что это связано с отношением parent/child в объекте RepositoryEntity. Для целей этого выбора я на самом деле требую только родительский объект.

Любые предложения?

+0

Как выглядит код, вызывающий ленивые нагрузки? Что вы повторяете? –

+0

Можете ли вы опубликовать «много многозадачных утверждений», которые вы видите, а также полные сопоставления для указанных выше четырех объектов? Все ли эти выборки основаны только на этих четырех таблицах? Существуют ли, возможно, другие «нетерпеливые» сопоставленные ассоциации этих организаций? – ChssPly76

+0

@ ChssPly76 - Я добавил UML и спящий режим.Если этого недостаточно, сообщите мне. –

ответ

0

Если вы не сопоставили коллекции как лениво загруженные, то при захвате объекта, вне зависимости от дополнительного HQL, он будет генерировать несколько выборок. Измените сопоставления подключений на ленивую загрузку. Кроме того, если connectionDetails никогда не может быть пустым, я предлагаю вам изменить последнее соединение на левое соединение.

+0

Я не понимаю, почему он будет генерировать дополнительные удары базы данных? HQL явно заявляет «нетерпеливо» на каждом из соединений, поэтому он должен иметь возможность извлекать все данные с помощью одного выбора, не так ли? –

+0

Я не могу объяснить механизм этого. Просто мой опыт заключается в том, что независимо от HQL, если вы наберете коллекцию ненавязчиво, она будет выбирать N + 1. – Jherico

0

Первый SQL, который вы опубликовали, - это тот, который вы ожидали (не считая inner join users, отсутствующего в вашем «ожидаемом» SQL, но он присутствует в вашем 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, который вы опубликовали, и вы увидите, что он применяется к . ВСЕ столбцы возвращаются по всем таблицам, что делает его довольно бессмысленным.

Смежные вопросы