Я пытаюсь привести в порядок какое-то плохо настроенное и очень старое отображение 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, который запускает генерацию байт-кода.
Есть ли какой-нибудь способ остановить количество запросов?