我有一个长时间运行(但相当简单)的应用程序,它使用Hibernate (通过JPA)。它在运行过程中经历了相当戏剧性的减速。我已经能够将范围缩小到偶尔需要一个entityManager.clear()
调用。当Hibernate的实体管理器跟踪100,000个实体时,它比仅跟踪少数几个实体慢约100倍(请参见下面的结果)。我的问题是:为什么在跟踪大量实体时会如此缓慢?还有没有其他办法绕过它呢?
!更新:我已经能够将范围缩小到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();
..。但这似乎是一个相当丑陋的解决方案--将知道事物是否被刷新到query方法的责任传递给query方法,而不是将其保留在更新方法中。这也意味着我必须在所有查询方法上设置刷新模式来提交,或者更有可能的是,在EntityManager上设置它。
这让我想知道:这是预期的行为吗?我在刷新或定义实体的方式上做错了什么吗?或者这是Hibernate的限制(或者可能是Hibernate中的bug )?
我用来隔离问题的示例代码如下:
测试实体
@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个。name列上有一个索引。
Place place = em.createQuery(
"select p from Place p where p.name = :name", Place.class)
.setParameter("name", name)
.getSingleResult();
为了进行比较,并确保它不在数据库中,我对一个单独的随机选择的5000个地名运行了以下基于JDBC的查询(在em.unwrap(Session.class).doWork(...)
下):
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查询期间(没有任何清晰调用),java进程将内核的利用率锁定在接近100%的水平。JVM堆大小从未超过500MB。在查询期间也有大量的GC活动,但是CPU利用率显然是由Hibernate代码控制的。
https://stackoverflow.com/questions/10143880
复制相似问题