[英]Hibernate making N+1 queries to init lazy collection in superclass
I'm trying to whip some badly configured and very old Hibernate mapping into shape.我正在尝试将一些配置不当且非常旧的 Hibernate 映射成形状。 It's still using hbm.xml to configure so I hope you can read it, but at least we are using a fairly recent Hibernate version, 5.2.12.
它仍然使用 hbm.xml 进行配置,所以我希望你能阅读它,但至少我们使用的是相当新的 Hibernate 版本,5.2.12。
This is cut down from the original to show the essential features.这是从原始内容中删除的,以显示基本特征。 The base class RepoResource has a bidirectional one-to-many collection of RepoAccessEvent which we make extra-lazy.
基本的 class RepoResource 有一个双向的一对多的 RepoAccessEvent 集合,我们将其变得非常懒惰。 The subclass RepoFileResource has a "data" property which we make extra-lazy because it's a blob.
子类 RepoFileResource 有一个“数据”属性,因为它是一个 blob,所以我们把它变得格外懒惰。
<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>
I notice that when we do a list() on a criteria and we load a bunch of the RepoFileResources, there's a query like this for each resource:我注意到,当我们根据条件执行 list() 并加载一堆 RepoFileResources 时,每个资源都有一个这样的查询:
select count(id) from AccessEvent where resource_id = ?
I have been doing some profiling to discover where these queries are getting run, and the stack goes through some generated methods (it appears that we are using Javassist):我一直在做一些分析以发现这些查询在哪里运行,并且堆栈通过一些生成的方法(看来我们正在使用 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
Note the lines starting with ";!";注意以“;!”开头的行; the PojoEntityTuplizer is calling two generated methods including
$$_hibernate_clearDirtyCollectionNames()
which then makes a PersistentSet.size() call that runs the count query. PojoEntityTuplizer 正在调用两个生成的方法,包括
$$_hibernate_clearDirtyCollectionNames()
,然后调用 PersistentSet.size() 来运行计数查询。
I took a look at the code which does the bytecode generation, and apparently it wants to know the count of the accessEvents collection for every single instance leading to the N + 1 problem.我查看了生成字节码的代码,显然它想知道导致 N + 1 问题的每个实例的 accessEvents 集合的计数。 This seems insane.
这似乎很疯狂。 I have a lot of other persistent types which I'm not showing, but only this type generates the count queries, and the only difference I can think of is that it has the lazy attribute which triggers the bytecode generation.
我还有很多其他持久性类型我没有展示,但只有这种类型会生成计数查询,我能想到的唯一区别是它具有触发字节码生成的惰性属性。
Is there any way to stop the count queries from happening?有没有办法阻止计数查询的发生?
Answering my own question...after thinking about it, even if I could fix this with config or something, maybe the choice of association (bidirectional one-to-many) is just inappropriate.回答我自己的问题......经过考虑,即使我可以用配置或其他东西解决这个问题,也许关联的选择(双向一对多)只是不合适的。 This kind of association would be used more typically in a composition, when there are just a few child objects and you usually want to fetch them along with the parent using a fetch join IIRC.
当只有几个子对象并且您通常希望使用 fetch join IIRC 将它们与父对象一起获取时,这种关联将更典型地用于组合中。 But these access events are numerous and rarely used, and frankly a big headache.
但这些访问事件数量众多且很少使用,坦率地说是一个很头疼的问题。 I'm thinking of just making it unidirectional many-to-one, from AccessEvent to Resource.
我正在考虑将其设为单向多对一,从 AccessEvent 到 Resource。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.