jpa AbstractFlushingEventListener中的Hibernate刷新速度减慢

6ju8rftf  于 2023-05-23  发布在  其他
关注(0)|答案(1)|浏览(93)

我遇到了一个问题,hibernate flushing策略在处理订单上的一组行项目时会随着时间的推移而变慢。当后续更新被调用到JBossSeam更新时,速度会变慢。
代码的业务流程接受一个有n个行项目的订单,为业务目的修改每个行项目,对该行项目调用seam/hibernate update(),然后移动到下一个行项目。在Hibernate中,当调用update()时,刷新策略立即响应。但是当代码沿着行项目列表向下移动时,Hibernate会变慢(几乎呈指数级)。在启用日志记录的情况下,我可以看到速度减慢从几秒开始,然后是一两分钟,然后是5分钟以上,然后是20分钟以上。全部处理集合中的相同订单行项目。它不会更改集合的大小,也不会添加或删除任何项。
我启用了hibernate事务的跟踪,并在输出中发现org.hibernate.event.def.AbstractFlushingEventListener在调用以下对象之间的某个地方遇到了问题:

prepareCollectionFlushes(session);

flushEntities(event);

根据Hibernate创建的日志条目,我追踪到了这两个方法调用之间的问题:

2014-12-02 17:50:30,808 DEBUG [org.hibernate.engine.CollectionEntry] (http-0.0.0.0-8443-2) Collection dirty: [com.companioncabinet.ccs.domain.data.Job.jobPhases#10324859]

2014-12-02 18:19:09,015 TRACE [org.hibernate.event.def.AbstractFlushingEventListener] (http-0.0.0.0-8443-2) Flushing entities and processing referenced collections

请注意17:50的消息组,然后是18:19的时间间隔。所有代码都在Hibernate的AbstractFlushingEventListner中。我检查了Hibernate源代码,发现它们的代码看起来像这样,导致上面的消息:

prepareCollectionFlushes(session);   
// now, any collections that are initialized   
// inside this block do not get updated - they   
// are ignored until the next flush   

persistenceContext.setFlushing(true);   
try {   
   flushEntities(event);   
   flushCollections(session);   
}   
   finally {   
   persistenceContext.setFlushing(false);   
}

显示的消息来自:

prepareCollectionFlushes(session);

flushingEntities(event);

preFlush方法调用CollectionEntry.preFlush,生成“Collectiondirty”消息。
以下是所有涉及的方法:

prepareCollectionFlushes

/**  
* Initialize the flags of the CollectionEntry, including the  
* dirty check.  
*/   
private void prepareCollectionFlushes(SessionImplementor session) throws HibernateException {   
   // Initialize dirty flags for arrays + collections with composite elements   
   // and reset reached, doupdate, etc.   
   log.debug("dirty checking collections");   
   final List list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );   
   final int size = list.size();   
   for ( int i = 0; i < size; i++ ) {   
   Map.Entry e = ( Map.Entry ) list.get( i );   
      ( (CollectionEntry) e.getValue() ).preFlush( (PersistentCollection) e.getKey() );   
   }   
}

preFlush

public void preFlush(PersistentCollection collection) throws HibernateException {

    boolean nonMutableChange = collection.isDirty() && 
            getLoadedPersister()!=null && 
            !getLoadedPersister().isMutable();
    if (nonMutableChange) {
        throw new HibernateException(
                "changed an immutable collection instance: " + 
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    dirty(collection);

    if ( log.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
        log.debug(
                "Collection dirty: " +
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    setDoupdate(false);
    setDoremove(false);
    setDorecreate(false);
    setReached(false);
    setProcessed(false);
}

肮脏

/**
 * Determine if the collection is "really" dirty, by checking dirtiness
 * of the collection elements, if necessary
 */
private void dirty(PersistentCollection collection) throws HibernateException {

    boolean forceDirty = collection.wasInitialized() &&
            !collection.isDirty() && //optimization
            getLoadedPersister() != null &&
            getLoadedPersister().isMutable() && //optimization
            ( collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization
            !collection.equalsSnapshot( getLoadedPersister() );

    if ( forceDirty ) {
        collection.dirty();
    }

}

上面代码块中的**collection.dirty()**将PersistenceCollection dirty设置为true。

flushEntities

/**  
* 1. detect any dirty entities  
* 2. schedule any entity updates  
* 3. search out any reachable collections  
*/   
private void flushEntities(FlushEvent event) throws HibernateException {   
   log.trace("Flushing entities and processing referenced collections");   
   ...remainder of code omitted since the delay happens before the above log entry appears

所有的代码都在手上,没有任何明确的事情发生。在延迟期间,代码中没有发生任何事情。一切似乎都停止了。
数据库是PostgreSQL。我已启用所有查询的日志记录。当我将最后一个查询与延迟之前的查询对齐时,它看起来像是重新加载了所有脏实体。当我运行记录的select时,它只需要不到1毫秒。
我知道这些都是旧版本,“升级”是要做的事情。但是,指出特定的缺陷、bug或通常已知的性能问题作为触发升级的催化剂是很好的。
感谢您的关注!

orm.xml

<persistence-unit-metadata>
    <persistence-unit-defaults>
        <entity-listeners>
            <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
        </entity-listeners>
    </persistence-unit-defaults>
</persistence-unit-metadata>

persistence.xml

<persistence-unit name="DS1" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/DS1</jta-data-source>
    <properties>
        <property name="hibernate.archive.autodetection" value="class" />
        <property name="hibernate.search.default.indexBase" value="/home/lucene/DS1" />
         <!-- Binds the EntityManagerFactory to JNDI where Seam can look it up.
        This is only relevant when the container automatically loads the persistence unit, as is the case in JBoss AS 5. -->
        <property name="jboss.entity.manager.factory.jndi.name" value="java:/DS1EntityManagerFactory"/>
    </properties>

</persistence-unit>

hibernate.properties为空
ds.xml

<local-tx-datasource>
    <jndi-name>DS1</jndi-name>
    <connection-url>jdbc:postgresql://127.0.0.1:5432/data</connection-url>
    <driver-class>org.postgresql.Driver</driver-class>
    <user-name>a_user</user-name>
    <password>a_pw</password>
    <idle-timeout-minutes>90</idle-timeout-minutes>
    <new-connection-sql>select 1</new-connection-sql>
    <check-valid-connection-sql>select 1</check-valid-connection-sql>
</local-tx-datasource>

DB日志摘录

2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...
xdnvmnnf

xdnvmnnf1#

持久化上下文越大,它所花费的时间就越多to verify all dirty properties
默认的自动脏检查机制必须遍历当前附加到当前正在运行的会话的所有实体的所有属性。
如果你想加快速度,你需要:

  1. switch to a custom dirty checking mechanism
  2. use bytecode enhancing(其中didn't seem to work on latest Hibernate versions

更新

你的一句话引起了我的注意:
代码的业务流程接受一个有n个行项目的订单,为业务目的修改每个行项目,对该行项目调用seam/hibernate update(),然后移动到下一个行项目。在Hibernate中,当调用update()时,刷新策略立即响应。
如果你加载N行项目,你不;只要会话仍处于活动状态,就不需要运行更新。所有更改将由脏检查机制自动检测。
如果您混合管理实体更改和问题选择,则AUTO flush mode will trigger a flush prior to any HQL query。因此,最好安排您的代码首先进行选择,并将修改留给事务的最后一部分。
您还可以将hibernate flush模式设置为COMMIT:

session.setFlushMode(FlushMode.COMMIT);

相关问题