簡體   English   中英

Hibernate 進行 N+1 查詢以在超類中初始化惰性集合

[英]Hibernate making N+1 queries to init lazy collection in superclass

我正在嘗試將一些配置不當且非常舊的 Hibernate 映射成形狀。 它仍然使用 hbm.xml 進行配置,所以我希望你能閱讀它,但至少我們使用的是相當新的 Hibernate 版本,5.2.12。

這是從原始內容中刪除的,以顯示基本特征。 基本的 class 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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM