【问题标题】:Hibernate Batch Update - Entities are not updatedHibernate 批量更新 - 实体未更新
【发布时间】: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();
    }
}

当进程运行时,看起来有些实体正在分离,导致两种症状:

  1. 尝试获取惰性数据时出现异常:org.hibernate.LazyInitializationException - could not initialize proxy - no Session
  2. 当不需要延迟加载时 - 仅更新 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


    【解决方案1】:

    问题是您正在清除包含所有托管实体的整个会话,使它们分离。

    如果您只处理部分数据,请确保只获取它们,然后您可以轻松清除整个会话并获取下一批数据并进行相同的计算。

    Article on LazyInitializationException 澄清一下。

    【讨论】:

    • 感谢@galovics,这很有意义。 Hibernate's documentation 表示这可以通过使用可滚动的结果集来解决(因此只有更新的实体会从会话缓存中清除)。问题是我的逻辑必须在执行计算过程之前预取所有数据,因为某些计算依赖于其他预取的实体。在这种情况下,您认为哪个更好 - 使用 session.attach(entity) 或使用 session.flush() 而不使用 session.clear
    • 您可以使用Session#evict 将实体从持久化上下文中移除。如果您使用的是 EntityManager,则可以使用 detach 方法。
    • 逻辑有点复杂,因为计算 Entity3 可能需要使用 Entity2 & Entity1 中的数据,这可能需要延迟加载。如果我驱逐或分离 Entity2 或 Entity1,Entity3 的计算将失败。
    • 请阅读我的文章,你想在这个案例中使用JOIN FETCH
    • 如果您对额外的内存占用和 CPU 时间浪费感到满意,那么您无需清除会话即可。 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-16
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 1970-01-01
    • 2012-07-11
    • 2013-07-11
    相关资源
    最近更新 更多