java - Hibernate 进行 N+1 查询以初始化父类(super class)中的惰性集合

标签 java performance hibernate

我正在尝试将一些配置错误且非常旧的 Hibernate 映射打造成形状。 它仍然使用 hbm.xml 进行配置,因此我希望您可以阅读它,但至少我们使用的是相当新的 Hibernate 版本 5.2.12。

这是从原始内容中删减的内容,以显示基本功能。基类 RepoResource 有一个双向一对多的 RepoAccessEvent 集合,我们将其设置为额外的惰性。子类 RepoFileResource 有一个“data”属性,我们将其设置为特别惰性,因为它是一个 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 集合的计数。这看起来很疯狂。我有很多其他持久类型,我没有显示,但只有这种类型生成计数查询,我能想到的唯一区别是它具有触发字节码生成的惰性属性。

有什么办法可以阻止计数查询的发生吗?

最佳答案

回答我自己的问题......经过思考,即使我可以通过配置或其他方式解决这个问题,也许关联的选择(双向一对多)是不合适的。当只有几个子对象并且您通常希望使用获取连接 IIRC 来获取它们和父对象时,这种关联更常用于组合中。但这些访问事件数量众多且很少使用,坦白说很让人头疼。我正在考虑将其设为单向多对一,从 AccessEvent 到 Resource。

关于java - Hibernate 进行 N+1 查询以初始化父类(super class)中的惰性集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58036447/

相关文章:

java - 悲观锁定因 ObjectOptimisticLockingFailureException 而失败

hibernate - Spring + hibernate : transaction commits but data is not persisted

java - Android map View 未加载内容

java - 使用java在spark core中读取/写入avro文件

java - 无法在 Eclipse Kepler 中使用 JD-Eclipse 反编译类

java - 如何在同一个jvm中运行两个或多个应用程序

java - 有没有办法查看 L2 hibernate 缓存?

PHP:JSON 或 XML 解析器更快吗?

performance - 如何在大空间尺度上加速 A* 算法?

java - @Converter 注释类未被 jpa 自动检测到