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

问题描述 投票:0回答:2

我正在使用 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
中的事务中运行(是的,这是一个 Web 应用程序)。

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

在仅限 Hibernate 的环境中,可以通过调用

org.hibernate.Session#clear()
轻松解决此问题。

现在,问题:

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

谢谢您的回答。

java hibernate spring orm
2个回答
48
投票

什么时候是

clear()
的好时机?性能成本大吗?

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

13.1。批量插入

使新对象持久化时 刷新()然后清除()会话 定期以控制尺寸 一级缓存。

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()

只要您

flush()
待处理的更改不丢失它们(除非这是您想要的),我没有看到任何问题(您当前的代码将每 100 个循环丢失一个创建,但也许这只是一些伪代码)。

如果上述问题的答案为真,假设在循环内调用

foo
,对象
clear()
会发生什么情况?如果
foo.foo()
是延迟加载方法怎么办?

调用

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


2
投票

我只是想指出,清除会话后,如果您想继续使用会话中的某些对象,则必须

Session.refresh(obj)
才能继续。

否则你会得到以下错误:

org.hibernate.NonUniqueObjectException
© www.soinside.com 2019 - 2024. All rights reserved.