Hibernate делает N + 1 запросов для инициации отложенной коллекции в суперклассе - PullRequest
1 голос
/ 21 сентября 2019

Я пытаюсь привести в порядок какое-то плохо настроенное и очень старое отображение Hibernate.Он все еще использует hbm.xml для настройки, поэтому я надеюсь, что вы можете прочитать его, но по крайней мере мы используем довольно свежую версию Hibernate, 5.2.12.

Это сокращено от оригинала, чтобы показать основные функции,Базовый класс RepoResource содержит двунаправленную коллекцию RepoAccessEvent «один ко многим», которую мы делаем очень ленивой.У подкласса RepoFileResource есть свойство «data», которое мы создаем очень ленивым, потому что это большой двоичный объект.

<hibernate-mapping>
    <class abstract="true" 
            table="Resource" 
            name="com.example.RepoResource" batch-size="1000">
        <id name="id" type="long">
            <generator class="native"/>
        </id>
        <natural-id mutable="true">
            <property name="name" not-null="true" length="200" type="string" column="name"/>
            <many-to-one column="parent_folder" name="parent" outer-join="auto"/>
        </natural-id>
        <set inverse="true" cascade="save-update" name="accessEvents" outer-join="auto" batch-size="1000" lazy="extra">
            <key column="resource_id"/>
            <one-to-many class="com.example.RepoAccessEvent"/>
        </set>
    </class>
  <class table="AccessEvent" name="com.example.RepoAccessEvent" batch-size="1000">
    <id name="id" type="long" unsaved-value="0">
      <generator class="native"/>
    </id>
    <property name="eventDate" column="event_date" type="timestamp" not-null="true" index="access_date_index"/>
    <many-to-one name="resource" 
                 column="resource_id" class="com.example.RepoResource"
                 not-null="true"
                 index="access_res_index"/>
  </class>

    <joined-subclass 
            name="com.example.RepoFileResource"
            extends="com.example.RepoResource"
            table="FileResource" batch-size="1000">
        <key column="id"/>
        <property name="data" type="blob" length="20971520" column="data" lazy="true"/>
        <property name="fileType" length="20" type="string" column="file_type"/>
        <many-to-one column="reference" name="reference" class="com.example.RepoFileResource" />
    </joined-subclass>
<hibernate-mapping>

Я замечаю, что когда мы выполняем list () по критериям и загружаем кучу RepoFileResourcesесть запрос, подобный следующему для каждого ресурса:

select count(id) from AccessEvent where resource_id = ?

Я провел некоторое профилирование, чтобы выяснить, где выполняются эти запросы, и стек проходит через некоторые сгенерированные методы (кажется, что мы используем Javassist):

org.apache.commons.dbcp.DelegatingPreparedStatement.executeQuery() DelegatingPreparedStatement.java:96  <2 recursive calls>
org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(PreparedStatement) ResultSetReturnImpl.java:60
org.hibernate.persister.collection.AbstractCollectionPersister.getSize(Serializable, SharedSessionContractImplementor) AbstractCollectionPersister.java:1943
org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork() AbstractPersistentCollection.java:157
org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork() AbstractPersistentCollection.java:146
org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection$LazyInitializationWork) AbstractPersistentCollection.java:247
org.hibernate.collection.internal.AbstractPersistentCollection.readSize() AbstractPersistentCollection.java:145
!! org.hibernate.collection.internal.PersistentSet.size() PersistentSet.java:143
!! com.example.RepoFileResource.$$_hibernate_clearDirtyCollectionNames() RepoFileResource.java
!! com.example.RepoFileResource.$$_hibernate_clearDirtyAttributes() RepoFileResource.java
!! org.hibernate.tuple.entity.PojoEntityTuplizer.afterInitialize(Object, SharedSessionContractImplementor) PojoEntityTuplizer.java:297
org.hibernate.persister.entity.AbstractEntityPersister.afterInitialize(Object, SharedSessionContractImplementor) AbstractEntityPersister.java:4635
org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(Object, EntityEntry, boolean, SharedSessionContractImplementor, PreLoadEvent) TwoPhaseLoad.java:278
org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(Object, boolean, SharedSessionContractImplementor, PreLoadEvent) TwoPhaseLoad.java:125
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(PreLoadEvent, ResultSetProcessingContextImpl, List) AbstractRowReader.java:238
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(ResultSetProcessingContextImpl, List) AbstractRowReader.java:209
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSet, SharedSessionContractImplementor, QueryParameters, NamedParameterContext, boolean, boolean, ResultTransformer, List) ResultSetProcessorImpl.java:133
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(SharedSessionContractImplementor, QueryParameters, LoadQueryDetails, boolean, ResultTransformer, List) AbstractLoadPlanBasedLoader.java:122
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(SharedSessionContractImplementor, QueryParameters, LoadQueryDetails, boolean, ResultTransformer) AbstractLoadPlanBasedLoader.java:86
org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(Serializable, Object, SharedSessionContractImplementor, LockOptions) AbstractLoadPlanBasedEntityLoader.java:167
org.hibernate.loader.entity.plan.LegacyBatchingEntityLoaderBuilder$LegacyBatchingEntityLoader.load(Serializable, Object, SharedSessionContractImplementor, LockOptions) LegacyBatchingEntityLoaderBuilder.java:124
org.hibernate.persister.entity.AbstractEntityPersister.load(Serializable, Object, LockOptions, SharedSessionContractImplementor) AbstractEntityPersister.java:4083
org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister) DefaultLoadEventListener.java:508
org.hibernate.event.internal.DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) DefaultLoadEventListener.java:478
org.hibernate.event.internal.DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) DefaultLoadEventListener.java:219
org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) DefaultLoadEventListener.java:278
org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(EntityPersister, LoadEvent, LoadEventListener$LoadType) DefaultLoadEventListener.java:121
org.hibernate.event.internal.DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) DefaultLoadEventListener.java:89
org.hibernate.internal.SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) SessionImpl.java:1239
org.hibernate.internal.SessionImpl.access$1900(SessionImpl, LoadEvent, LoadEventListener$LoadType) SessionImpl.java:203
org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.doLoad(Serializable) SessionImpl.java:2804
org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(Serializable) SessionImpl.java:2778
org.hibernate.internal.SessionImpl$NaturalIdLoadAccessImpl.load() SessionImpl.java:3105
org.hibernate.internal.SessionImpl.list(Criteria) SessionImpl.java:1865
org.hibernate.internal.CriteriaImpl.list() CriteriaImpl.java:370
org.springframework.orm.hibernate5.HibernateTemplate$35.doInHibernate(Session) HibernateTemplate.java:1051
org.springframework.orm.hibernate5.HibernateTemplate$35.doInHibernate(Session) HibernateTemplate.java:1040
org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateCallback, boolean) HibernateTemplate.java:361
org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateCallback) HibernateTemplate.java:328
org.springframework.orm.hibernate5.HibernateTemplate.findByCriteria(DetachedCriteria, int, int) HibernateTemplate.java:1040
org.springframework.orm.hibernate5.HibernateTemplate.findByCriteria(DetachedCriteria) HibernateTemplate.java:1032

Обратите внимание на строки, начинающиеся с "!!";PojoEntityTuplizer вызывает два сгенерированных метода, включая $$_hibernate_clearDirtyCollectionNames(), который затем выполняет вызов PersistentSet.size (), который выполняет запрос подсчета.

Я взглянул на код, который выполняет генерацию байт-кода, и, очевидно, он хочетузнать количество коллекции accessEvents для каждого отдельного экземпляра , что приводит к проблеме N + 1.Это кажется безумным.У меня есть много других постоянных типов, которые я не показываю, но только этот тип генерирует запросы на подсчет, и единственное отличие, о котором я могу подумать, состоит в том, что он имеет атрибут lazy, который запускает генерацию байт-кода.

Есть ли какой-нибудь способ остановить количество запросов?

1 Ответ

0 голосов
/ 22 сентября 2019

Отвечая на мой собственный вопрос ... подумав об этом, даже если бы я мог исправить это с помощью конфигурации или чего-то еще, может быть, выбор ассоциации (двунаправленный один-ко-многим) просто неуместен.Этот тип ассоциации будет более типично использоваться в композиции, когда имеется всего несколько дочерних объектов, и вы обычно хотите получить их вместе с родителем, используя соединение извлечения IIRC.Но эти события доступа многочисленны и редко используются, и, откровенно говоря, большая головная боль.Я подумываю просто сделать его однонаправленным «многие-к-одному», от AccessEvent до Resource.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...