Hibernate:即使我在配置文件中设置了 batch_size,为什么还要手动 flush()?

Hibernate: Why should I manually flush() even if I set batch_size in configuration file?

我正在学习使用 java 的 hibernate 5.2.10。我从网上的一些教程开始,但遇到了以下问题。

使用批处理时,我看到的所有教程都是先在配置文件中设置hibernate.jdbc.batch_size。之后的代码类似于:

Session session = SessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<1000000; i++ ) 
{
    Student student = new Student(.....);
    session.save(employee);
    if( i % 50 == 0 ) // Same as the JDBC batch size
    { 
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
tx.commit();
session.close();

为什么我应该手动执行 flush()clear()?因为我已经在配置文件中设置了 hibernate.jdbc.batch_size ,所以这不是应该由 hibernate 自动完成的事情吗?

对我来说,我好像是在手动批处理我的操作,那么为什么我必须设置 hibernate.jdbc.batch_size 的值呢?

  //flush a batch of inserts and release memory:
    session.flush();
    session.clear();

您应该调用 flush() 方法强制生成 sql 查询并执行它们。如果你不手动调用 flush() ,如果调用 hibernate 并提交事务时间。

您应该调用 clear() 方法从持久性上下文中删除有关实体的信息以避免 OutOffMemeoryException ,因为您可能有大量实体,它们可能会消耗大量内存。

您应该手动控制批处理操作,因为并非所有休眠操作都需要批处理模式。

"Why should I be doing flush() and clear() manually? Isn't this something that should be done automatically by hibernate since " - 主要是,hibernate 在提交时执行此操作。方法 flush() 和 clear() 独立于使用 batch_size ,无论您是否有批处理模式,您都可以调用它们。

您可能会遇到以下情况:在 dao 方法内部调用 N 次 flush() - 当您需要实体和数据库级别之间的同步时,调用 flush() - 当您不再使用实体时,并且想要清理会话。

根据您的示例,您有 1000000 个元素。在不调用 flush 和 clear 的情况下,您会将所有 1000000 个元素的信息保存在一级缓存中。您在循环中的每个新迭代中将一个一个的新实体添加到会话上下文中,但是在批次准备好/准备好之后您不需要此信息,这就是为什么您应该调用 flush , clear - 删除您不再需要的信息.

在配置中指定 JDBC batch_size 值与手动控制持久性上下文的 flush/clear 是两种独立的策略,服务于截然不同的目的。

flush()clear() 结合使用的主要目标是在保存学生记录时最大限度地减少 PersistenceContext 使用的 java 应用程序端的内存消耗。重要的是要记住,当您使用有状态的 Session 作为示例时,Hibernate 在内存中维护实体的 attached/managed 副本,因此定期清除并将其刷新到数据库很重要间隔以避免 运行 内存不足或影响性能。

JDBC batch_size 设置本身会影响实际驱动程序将语句刷新到数据库以提高性能的频率。让我们举一个稍微修改过的例子:

Session session = sessionFactory.openSession();
try {
  session.getTransaction().begin();
  for ( int i = 0; i < 10000; ++i ) {
    Student student = new Student();
    ...        
    session.save( student );
  }
  session.getTransaction().commit();
}
catch( Throwable t ) {
  if ( session.getTransaction().getStatus() == TransactionStatus.ACTIVE ) {
    session.getTransaction().rollback();
  }
  throw t;
}
finally {
  session.close();
}

如您所见,我们在这里没有使用 flush()clear()

这里发生的是,当 Hibernate 在提交时执行刷新时,驱动程序将向数据库批量发送 batch_size 次插入,而不是单独发送。因此,如果 batch_size 为 250,则不会发送 10,000 个网络数据包,它只会发送 40 个数据包。

现在重要的是要认识到有一些因素可以 禁用 批处理,例如使用基于身份的标识符,如 IDENTITYAUTO_INCREMENT。为什么?

这是因为为了让 Hibernate 将实体存储在 PersistenceContext 中,它必须知道实体的 ID,并且在使用基于 IDENTITY 的标识符生成时获取该值的唯一方法是实际查询数据库每次插入操作后的值。因此,插入不能被批处理。

这正是执行批量插入操作的人经常观察到性能不佳的原因,因为他们没有意识到他们选择的标识符生成策略可能产生的影响。

当您想要优化批量加载时,最好使用某种类型的缓存序列生成器或某些手动分配的应用程序标识符。

现在回到您使用 flush()clear() 的示例,同样的问题也适用于标识符生成策略。如果您希望将这些操作 bulk/batch 发送到数据库,请注意您用于 Student.

的标识符策略

回答您在描述中提出的问题,正如我所研究的那样,flush()-ing batch/transaction 不同于 commit()-ing 事务。

您在每 50 个块后刷新事务,这意味着您正在 同步 将事务作为 50 个批次同步到数据库。50 个块已与数据库同步,但是尚未提交。
但是当你在配置文件中定义 batch-size 时,你是在告诉 Hibernate commit 40 个批次(假设你在 conf 文件中设置了批次大小 40。)