我有一个使用 Hibernate(通过 JPA)的长时间运行(但相当简单)的应用程序。它在运行时正在经历相当剧烈的减速。我已经能够缩小到需要偶尔entityManager.clear()
打电话的范围。当 Hibernate 的实体管理器跟踪 100,000 个实体时,它比只跟踪少数实体时慢约 100 倍(见下面的结果)。 我的问题是:为什么Hiberate 在跟踪很多实体时会减慢这么多?还有其他方法吗?
!!!更新:我已经能够将其缩小到 Hibernate 的自动刷新代码。!!!
具体到org.hibernate.event.internal.AbstractFlushingEventListener
'flushEntities()
方法(至少在 Hibernate 4.1.1.Final 中)。其中有一个循环遍历持久性上下文中的所有实体,围绕刷新每个实体执行一些广泛的检查(即使在我的示例中所有实体都已刷新!)。
因此,部分回答了我的问题的第二部分,可以通过FlushModeType.COMMIT
在查询上设置刷新模式来解决性能问题(请参阅下面的更新结果)。例如
Place place = em.createQuery("from Place where name = :name", Place.class)
.setParameter("name", name)
.setFlushMode(FlushModeType.COMMIT) // <-- yay!
.getSingleResult();
...但这似乎是一个相当丑陋的解决方案——将了解事物是否刷新到查询方法而不是将其保留在更新方法中的责任。这也几乎意味着我必须在所有查询方法上将刷新模式设置为 COMMIT,或者更有可能在 EntityManager 上设置它。
这让我想知道:这是预期的行为吗?我在冲洗或如何定义实体方面做错了吗?或者这是 Hibernate 的限制(或可能存在的错误)?
我用来隔离问题的示例代码如下:
测试实体
@Entity @Table(name="place") @Immutable
public class Place {
private Long _id;
private String _name;
@Id @GeneratedValue
public Long getId() { return _id; }
public void setId(Long id) { _id = id; }
@Basic(optional=false) @Column(name="name", length=700,
updatable=false, nullable=false, unique=true,
columnDefinition="varchar(700) character set 'ascii' not null")
public String getName() { return _name; }
public void setName(String name) { _name = name; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { return getName().hashCode(); }
}
基准代码
我的测试代码生成 100000 个随机地名并插入它们。然后按名称随机查询其中的 5000 个。名称列上有一个索引。
Place place = em.createQuery(
"select p from Place p where p.name = :name", Place.class)
.setParameter("name", name)
.getSingleResult();
为了比较,并确保它不在数据库中,我在em.unwrap(Session.class).doWork(...)
一个单独的随机选择的 5000 个地名上运行了以下基于 JDBC 的查询(在 下):
PreparedStatement ps = c.prepareStatement(
"select id, name from place where name = ?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Place place = new Place();
place.setId(rs.getLong(1));
place.setName(rs.getString(2));
}
rs.close();
ps.close();
(注意,我确实为基准测试的 5000 个查询中的每一个创建并关闭了 PreparedStatement)。
结果
以下所有结果均超过 5000 个查询的平均值。JVM被给了-Xmx1G
Seconds/Query Approach
0.000160s JDBC
0.000286s Hibernate calling clear() after import and every 100 queries
0.000653s Hibernate calling clear() once after the import
0.012533s Hibernate w/o calling clear() at all
0.000292s Hibernate w/o calling clear(), and with flush-mode COMMIT
其他观察结果:在 Hibernate 查询期间(没有任何 clear 调用),java 进程将一个核心固定在接近 100% 的利用率。JVM 从未超过 500MB 堆。查询期间也有很多 GC 活动,但 CPU 利用率显然由 Hibernate 代码主导。