在大型事务中安全地清除休眠会话

2022-09-01 06:51:58

我正在使用Spring + Hibernate进行操作,该操作需要创建和更新数十万个项目。像这样:

{
   ...
   Foo foo = fooDAO.get(...);
   for (int i=0; i<500000; i++) {
      Bar bar = barDAO.load(i);
      if (bar.needsModification() && foo.foo()) {
         bar.setWhatever("new whatever");
         barDAO.update(bar);
         // commit here
         Baz baz = new Baz();
         bazDAO.create(baz);
         // if (i % 100 == 0), clear
      }
   }
}

为了保护自己免受中间更改的丢失,我立即在以下位置提交更改:barDAO.update(bar)

HibernateTransactionManager transactionManager = ...; // injected by Spring
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
transactionManager.commit(transactionStatus);

在这一点上,我不得不说整个进程都在包装(是的,这是一个webapp)的事务中运行。org.springframework.orm.hibernate3.support.ExtendedOpenSessionInViewFilter

这一切都很好,但有一个例外:经过几千次更新/提交后,整个过程变得非常慢,很可能是由于Spring/Hibernate保存的对象数量不断增加,内存膨胀。

在仅休眠环境中,通过调用 可以很容易地解决。org.hibernate.Session#clear()

现在,问题:

  • 什么时候是好时机?它是否具有较大的性能成本?clear()
  • 为什么对象不是自动的或已发布的/GCd?在提交后将它们保留在会话中有什么意义(在下一个迭代循环中,无论如何都无法访问它们)?我还没有做记忆转储来证明这一点,但我的良好感觉是,它们仍然存在,直到完全退出。如果这个问题的答案是“休眠缓存”,那么为什么在可用内存不足时缓存没有被刷新?barbaz
  • 直接调用是否安全/建议(考虑到整个Spring上下文,如延迟加载等)?是否有任何可用的弹簧包装/对应物来实现相同的目标?org.hibernate.Session#clear()
  • 如果上述问题的答案为真,那么假设在循环内部调用对象会发生什么?如果是延迟加载方法怎么办?fooclear()foo.foo()

感谢您的回答。


答案 1

什么时候是清除()的好时机?它是否具有较大的性能成本?

在刷新更改后,定期进行,理想情况下与 JDBC 批大小相同。文档描述了有关批处理的章节中的常见习语:

13.1. 批量插入

当创建新对象时,请持续 flush() 然后定期清除() 会话,以控制第一级缓存的大小。

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();

这不应该有性能成本,相反:

  • 它允许将要跟踪的物体数量保持在低水平(因此冲洗应该很快),
  • 它应该允许回收内存。

为什么像bar或baz这样的对象不会自动释放/ GCd?在提交后将它们保留在会话中有什么意义(在下一个迭代循环中,无论如何都无法访问它们)?

如果您不想跟踪实体,则需要显式地进行会话,仅此而已,这就是它的工作方式(人们可能希望在不“丢失”实体的情况下提交事务)。clear()

但据我所知,bar和baz实例应该在清除后成为GC的候选者。分析内存转储以查看究竟发生了什么会很有趣。

是否安全/建议直接调用 org.hibernate.Session#clear()

只要你对挂起的更改不丢失它们(除非这是你想要的),我不认为这有什么问题(你当前的代码会每100个循环松散一个创建,但也许它只是一些伪代码)。flush()

如果上述问题的答案是正确的,那么假设在循环内部调用了clear(),那么对象foo会发生什么?如果 foo.foo() 是一个延迟加载方法呢?

调用 clear() 会将所有加载的实例从会话中逐出,使它们成为分离的实体。如果后续调用需要“附加”实体,它将失败。


答案 2

我只是想指出,在清除会话后,如果要继续使用会话中的某些对象,则必须使用它们才能继续。Session.refresh(obj)

否则,您将收到以下错误:

org.hibernate.NonUniqueObjectException

推荐