题 在大事务中安全地清除Hibernate会话


我使用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);

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

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

在仅限Hibernate的环境中,通过调用可以轻松解决这个问题 org.hibernate.Session#clear()

现在,问题:

  • 什么时候开始呢? clear()?它的性能成本是否很高?
  • 为什么不是这样的对象 bar 要么 baz 自动发布/ GCd?在提交之后将它们保留在会话中有什么意义(在下一个迭代循环中它们无论如何都无法访问)?我没有做过记忆转储来证明这一点,但我的感觉是他们仍然在那里直到完全退出。如果答案是“Hibernate cache”,那么为什么缓存在可用内存不足时刷新?
  • 安全/建议打电话 org.hibernate.Session#clear() 直接(考虑到整个Spring上下文,延迟加载等事情)?有没有可用的Spring包装器/同类产品用于实现相同目的?
  • 如果回答上述问题是真的,那么对象会发生什么 foo, 假设 clear() 在循环中调用?如果 foo.foo() 是一种延迟加载方法?

谢谢你的回答。


31
2017-09-24 14:29


起源


我也在做多个大插页。偶尔添加代码来刷新和清除会话只会使我的代码运行速度提高4倍! - stephen.hanson


答案:


何时是清除()的好时机?它的性能成本是否很高?

在刷新更改后,定期(理想情况下与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();

而这应该没有表现 成本,au contraire:

  • 它允许保持跟踪物体的数量低(因此冲洗应该很快),
  • 它应该允许回收记忆。

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

你需要 clear() 如果您不想跟踪实体,那就明确会话,这就是它的工作原理(人们可能希望在没有“丢失”实体的情况下提交事务)。

但是从我所看到的情况来看,bar和baz实例应该在明确之后成为GC的候选者。分析内存转储以查看正在发生的事情会很有趣。

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

只要你 flush() 没有松散它们的挂起更改(除非这是你想要的),我没有看到任何问题(你的当前代码将每100个循环松散一次创建,但也许它只是一些伪代码)。

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

调用 clear() 驱逐所有已加载的实例 Session,使他们分离实体。如果后续调用要求实体“附加”,则它将失败。


44
2017-09-24 15:22



谢谢你澄清这个Pascal! - mindas
@mindas你很受欢迎。 - Pascal Thivent
+1使用“au contraire” - kommradHomer


我只想指出,在清除会话后,如果你想继续使用会话中的一些对象,你将不得不 Session.refresh(obj) 他们为了继续。

否则您将收到以下错误:

org.hibernate.NonUniqueObjectException

1
2017-11-10 21:51



为了更好的可读性,请使用``突出显示答案中的源代码 - Session.refresh(obj) - Michael Lihs