java - Hibernate 进行 N+1 查询以在超类中初始化惰性集合
问题描述
我正在尝试将一些配置错误且非常旧的 Hibernate 映射调整为形状。它仍然使用 hbm.xml 进行配置,所以我希望你能阅读它,但至少我们使用的是一个相当新的 Hibernate 版本,5.2.12。
这是从原始内容中删除的,以显示基本特征。基类 RepoResource 有一个双向的一对多的 RepoAccessEvent 集合,我们使之变得格外懒惰。子类 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>
我注意到,当我们根据条件执行 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() 来运行计数查询。
我查看了生成字节码的代码,显然它想知道导致 N + 1 问题的每个实例的 accessEvents 集合的计数。这似乎很疯狂。我还有很多其他持久性类型我没有展示,但只有这种类型会生成计数查询,我能想到的唯一区别是它具有触发字节码生成的惰性属性。
有没有办法阻止计数查询的发生?
解决方案
回答我自己的问题......经过考虑,即使我可以用配置或其他东西解决这个问题,也许关联的选择(双向一对多)只是不合适的。当只有几个子对象并且您通常希望使用 fetch join IIRC 将它们与父对象一起获取时,这种关联将更典型地用于组合中。但这些访问事件数量众多且很少使用,坦率地说是一个很头疼的问题。我正在考虑将其设为单向多对一,从 AccessEvent 到 Resource。
推荐阅读
- python - 如何在不更改路径环境变量的情况下安装 python 包?
- python - 两个`pandas.DataFrame`的向量化合并,在ID范围内
- javascript - 如何正确地在 Javascript 中对数组进行 URL 编码?
- php - 如何将日期的文本颜色更改为白色?
- antlr - ANTLR ParserInterpreter
- android - 我的按钮 onClick 进程在 Xamarin Android 中不起作用
- powerbi - Power BI - 提取转置备用行
- api - 请告诉我们使用 VrVideoView 的最低 API
- html - 我试图弄清楚如何在小屏幕(手机、平板电脑)上查看时删除顶部边距
- amazon-web-services - Pyspark 在查询 Hive hdfs 时使用 *.ec2.internal ip 而不是实际 ip 的问题