在调用clear()之前,Hibernate使用flushMode=AUTO查询速度要慢得多
我有一个长时间运行(但相当简单)的应用程序,它使用Hibernate(通过JPA)。它在运行过程中经历了相当大的减速。我已经能够缩小范围,只需要偶尔调用在调用clear()之前,Hibernate使用flushMode=AUTO查询速度要慢得多,hibernate,jpa,Hibernate,Jpa,我有一个长时间运行(但相当简单)的应用程序,它使用Hibernate(通过JPA)。它在运行过程中经历了相当大的减速。我已经能够缩小范围,只需要偶尔调用entityManager.clear()。当Hibernate的实体管理器跟踪100000个实体时,它比仅跟踪少数实体时慢约100倍(见下面的结果)我的问题是:为什么Hibrate在跟踪很多实体时会减速这么多?还有其他办法吗? !!!更新:我已经能够将这个范围缩小到Hibernate的自动刷新代码 特别是org.hibernate.event
entityManager.clear()
。当Hibernate的实体管理器跟踪100000个实体时,它比仅跟踪少数实体时慢约100倍(见下面的结果)我的问题是:为什么Hibrate在跟踪很多实体时会减速这么多?还有其他办法吗?
!!!更新:我已经能够将这个范围缩小到Hibernate的自动刷新代码强> 特别是
org.hibernate.event.internal.AbstractFlushingEventListener
的Flushenties()
方法(至少在hibernate 4.1.1.Final中)。其中有一个循环,它迭代持久性上下文中的所有实体,围绕刷新每个实体执行一些广泛的检查(即使在我的示例中所有实体都已刷新!)
因此,部分回答我问题的第二部分,可以通过在查询中将flush模式设置为FlushModeType.COMMIT
来解决性能问题(请参见下面的更新结果)。e、 g
Place-Place=em.createQuery(“from-Place-where-name=:name”,Place.class)
.setParameter(“名称”,名称)
.setFlushMode(FlushModeType.COMMIT)/也许您熟悉EntityManager
跟踪持久对象(即通过调用em.createQuery(…).getSingleResult()创建的对象)。它们在所谓的持久上下文或会话(Hibernate术语)中累积,并允许非常整洁的特性。例如,您可以通过调用mutator方法setName(…)
来修改对象,EntityManager
将在适当的时候将内存中的此状态更改与数据库同步(将发出更新语句)。这不需要调用显式的save()
或update()
方法。您只需像处理普通Java对象一样处理该对象,EntityManager
将负责持久性
为什么这样慢(er)?
首先,它确保内存中每个主键只有一个实例。这意味着,如果两次加载同一行,堆中将只创建一个对象(两个结果都是==
)。这很有意义——想象一下,如果您有同一行的两个副本,EntityManager
不能保证它可靠地同步Java对象,因为您可以独立地对这两个对象进行更改。如果需要跟踪的对象很多,那么可能还有很多其他低级操作最终会减慢Entitymanager的运行速度。clear()
方法实际上删除了持久上下文中的对象,使任务更容易(要跟踪的对象更少=操作更快)
你怎样才能避开它?
如果您的EntityManager
实现是Hibernate,您可以使用它来解决这些性能问题。我想你可以做到:
无状态会话=((会话)entityManager.getDelegate()).getSessionFactory().openStatelessSession()代码>
(注意!代码未测试,取自其他代码)
但我主要好奇的是,为什么Hibernate似乎会对查询进行O(n)甚至O(n^2)查找——似乎它应该能够在引擎盖下使用哈希表或二叉树来保持查询速度。请注意,当它跟踪100000个实体与100个实体时,会出现2个数量级的差异
O(n²)复杂性源于必须处理查询的方式。因为Hibernate在内部尽可能延迟更新和插入(利用机会将类似的更新/插入分组在一起,特别是在设置对象的多个属性时)
因此,在保存数据库中的查询对象之前,Hibernate必须检测所有对象更改并刷新所有更改。这里的问题是hibernate还有一些通知和拦截正在进行。因此,它迭代由持久性上下文管理的每个实体对象。即使对象本身是不可变的,它也可能包含可变对象甚至引用集合
此外,拦截机制允许您访问被认为是脏的任何对象,以允许您自己的代码实现额外的脏度检查或执行额外的计算,如计算总和、平均值、记录额外信息等
但让我们看一下代码:
用于在中准备查询结果的刷新调用:
DefaultFlushEventListener.onFlush(..)
->抽象FlushingeVentListener.flushEverythingToExecution(事件)
->AbstractFlushingEventListener.prepareEntityFlushes(..)
实施使用:
for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) {
EntityEntry entry = (EntityEntry) me.getValue();
Status status = entry.getStatus();
if ( status == Status.MANAGED || status == Status.SAVING || status == Status.READ_ONLY ) {
cascadeOnFlush( session, entry.getPersister(), me.getKey(), anything );
}
}
正如您所见,将检索并迭代持久性上下文中所有实体的映射
这意味着对于查询的每次调用,您都要迭代所有以前的结果以检查脏对象。甚至更多的cascadeOnFlush创建了一个新对象并做了更多的事情。下面是cascadeOnFlush的代码:
private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, Object anything)
throws HibernateException {
session.getPersistenceContext().incrementCascadeLevel();
try {
new Cascade( getCascadingAction(), Cascade.BEFORE_FLUSH, session )
.cascade( persister, object, anything );
}
finally {
session.getPersistenceContext().decrementCascadeLevel();
}
}
这就是解释。Hibernate只是在每次发出查询时检查由持久性上下文管理的每个对象
因此,对于阅读本文的每个人来说,这里是复杂性计算:
1.查询:0个实体
2.查询:1个实体
3.查询:2个实体
..
100查询:100个实体
.
..
100k+1查询:100k个条目
所以我们有O(0+1+2…+n)=O(n(n+1)/2)=O(n²)
这就解释了你的观察。为了保持较小的cpu和内存占用,hibernate管理的持久性上下文应尽可能小。让Hibernate管理100或1000个以上