【发布时间】:2017-10-06 16:40:27
【问题描述】:
我有一个批处理过程,它正在为一组实体重新计算数据。实体列表由hibernate从数据库中获取:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recalculateUserData(Long userId){
List<Entity> data = repository.getAllPendingRecalculation(userId);
List<Entity> recalculated = new LinkedList();
for (Entity entity : data){
recalculateEntity(entity, recalculated);
recalculated.add(entity);
flushIfNeeded(recalculated); //every 10 records
}
}
private void recalculateEntity(Entity entity, List<Entity> recalculated){
//do logic
}
private void flush(){
getSession().flush();
getSession().clear();
}
private void flushIfNeeded(List<Entity> data) {
int flushSize = 10
if (data.size() % flushSize == 0){
flush();
}
}
当进程运行时,看起来有些实体正在分离,导致两种症状:
- 尝试获取惰性数据时出现异常:
org.hibernate.LazyInitializationException - could not initialize proxy - no Session。 - 当不需要延迟加载时 - 仅更新 DB 中的前 10 条记录,即使
flushIfNeeded(...)工作正常。
在我第一次尝试时,我尝试通过在 recalculateEntity(...) 中调用 session#refresh(entity) 来解决它 - 这解决了延迟初始化问题,但 #2 中的问题仍然存在:
private void recalculateEntity(Entity entity){
getSession().refresh(entity);
//do logic
}
由于这还没有解决我考虑使用attach(entity)而不是refresh(entity)的问题:
private void recalculateEntity(Entity entity){
getSession().attach(entity);
//do logic
}
这似乎可行,但我的问题是:为什么这些实体首先会被分离?
(我使用的是 Hibernate 3.6.10)
更新
正如@galovics 解释的那样:
问题是您正在清除包含所有托管实体的整个会话,使它们分离。
Hibernate batch processing documentation 表示应该使用ScrollableResults 执行批量更新(这解决了这些问题),但在这种情况下,我必须在处理之前获取所有数据,因为实体计算可能依赖于已经存在的实体计算出来的。例如,计算实体#3 可能需要为实体#1 和实体#2 计算的数据。
对于这样的情况,使用Session#attach(entity)(如代码所示),使用Session#flush()而不使用Session#clear()会更好还是有更好的解决方案?
【问题讨论】:
标签: java hibernate lazy-loading lazy-initialization hibernate-batch-updates