3

我正在学习使用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.jdbc.batch_sizethen 的值呢?

4

3 回答 3

5

使用在配置中指定 JDBC batch_size 值与手动控制持久性上下文的刷新/清除是两种独立的策略,并且用于非常不同的目的。

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

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,它只会发送 40 个数据包,而不是发送 10,000 个网络数据包。

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

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

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

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

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

于 2017-07-10T17:01:58.513 回答
1
  //flush a batch of inserts and release memory:
    session.flush();
    session.clear();

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

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

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

“我为什么要手动执行flush() 和clear()?这不是应该由hibernate 自动完成的事情,因为” - 主要是hibernate 在提交时执行此操作。方法 flush() 和 clear() 独立于使用 batch_size ,无论你是否有批处理模式,你都可以调用它们。

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

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

于 2017-07-10T09:16:03.713 回答
0

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

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

于 2019-05-08T20:59:57.367 回答