Мне удалось выяснить это, так что другие люди, наконец, могут получить прямой ответ:
Подводя итог, я некоторое время путался в разнице между кешем второго уровня и кешем запросов; Ответ Джейсона технически верен, но почему-то меня это не задело. Вот как бы я это объяснил:
Кэш запроса отслеживает , какие объекты испускаются запросом. не кэширует весь набор результатов. Это эквивалентно выполнению Session.Load
на лениво загруженной сущности; он знает / ожидает, что кто-то существует, но не отслеживает любую другую информацию о нем, если не будет специально задан вопрос, в какой момент он фактически загрузит реальную сущность.
Кэш второго уровня отслеживает фактические данные для каждого объекта. Когда NHibernate необходимо загрузить любую сущность по ее идентификатору (в силу Session.Load
, Session.Get
, отношения с отложенной загрузкой или, в случае выше, «ссылки» сущности, которая является частью кэшированного запроса), он будет сначала загляните в кеш второго уровня.
Конечно, это имеет смысл в ретроспективе, но это не так очевидно, когда вы слышите, что термины «кэш запросов» и «кэш второго уровня» используются почти взаимозаменяемо во многих местах.
По сути, существует два набора из двух параметров, каждый из которых необходимо настроить, чтобы увидеть ожидаемые результаты с кэшированием запросов:
1. Включить оба кэша
В конфигурации XML это означает добавление следующих двух строк:
<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>
В Fluent NHibernate это так:
.Cache(c => c
.UseQueryCache()
.UseSecondLevelCache()
.ProviderClass<SysCacheProvider>())
Обратите внимание на UseSecondLevelCache
выше, потому что (на момент публикации) это никогда , упомянутое на Свободной странице вики NHibernate ; Есть несколько примеров включения кэша запросов, но не кэша второго уровня!
2. Включить кэширование для каждой сущности
Простое включение кэша второго уровня практически ничего не делает, и вот тут я и запутался. Кэш второго уровня должен быть не только включен, но настроен для каждого отдельного отдельного класса сущностей, который вы хотите кэшировать .
В XML это делается внутри элемента <class>
:
<cache usage="read-write"/>
В Fluent NHibernate (не-autop) это делается в конструкторе ClassMap
или в том месте, куда вы помещаете оставшуюся часть кода отображения:
Cache.ReadWrite().Region("Configuration");
Это должно быть сделано для каждой сущности, которая будет кэшироваться. Вероятно, можно настроить в одном месте как соглашение, но тогда вы в значительной степени упускаете возможность использовать регионы (и в большинстве систем вы не хотите кэшировать данные транзакций так же, как данные конфигурации).
И это все. Это действительно не так сложно сделать, но на удивление сложно найти хороший, полный пример, особенно для FNH.
И последнее замечание: естественным следствием этого является то, что делает стратегии непредсказуемого соединения / выборки очень непредсказуемыми при использовании с кешем запросов. Очевидно, что если NHibernate видит, что запрос кэшируется, он будет делать без усилий сначала проверить, кэшированы ли все или даже любые фактических сущностей. Он просто предполагает, что они есть, и пытается загрузить каждый из них по отдельности.
Это причина катастрофы SELECT N + 1; было бы не так уж и сложно, если бы NH заметил, что сущности отсутствуют в кэше второго уровня, а просто выполнил запрос нормально, как написано, с выборками, фьючерсами и так далее. Но это не делает этого; вместо этого он пытается загрузить каждую сущность, ее отношения, ее под-отношения, его под-под-отношения и т. д. по одному .
Таким образом, практически нет смысла использовать кеш запросов, если вы явно не включили кеширование для всех сущностей в графе весь , и даже тогда вы будетеЯ хочу быть очень осторожным (из-за истечения срока действия, зависимостей и т. д.), чтобы кэшированные запросы не переживали сущности, которые они должны извлекать, в противном случае вы просто ухудшите производительность.