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

时间:2017-07-10 08:58:34

标签: java hibernate batching

我正在学习使用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自动完成的,因为我已经在配置文件中设置了hibernate.jdbc.batch_size

对我来说,似乎我手动批量操作,那么为什么我必须设置hibernate.jdbc.batch_size的值呢?

3 个答案:

答案 0 :(得分:4)

在配置中指定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在提交时执行刷新时,驱动程序将批量发送批量插入数据到数据库而不是单独发送。因此,不是发送10,000个网络数据包,如果batch_size为250,则只发送40个数据包。

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

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

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

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

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

答案 1 :(得分:1)

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

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

你应该从持久化上下文中调用clear()方法来获取有关实体的删除信息,以避免OutOffMemeoryException,因为你可能拥有一个包含大量实体的butch并且它们可能会占用大量内存。

您应该手动控制批量操作,而不是所有hibernate的操作都需要批处理模式。

“我为什么要手动执行flush()和clear()?这不是hibernate自动完成的事情,因为” - 主要是hibernate在提交时执行它。方法flush()和clear()与使用batch_size无关,尽管你是否有批处理模式,你可以调用它们。

你可能有一个案例,当你在dao方法内部调用N次flush()时 - 你需要实体和数据库级别之间的同步,并调用flush() - 当你不再使用实体,并希望干净的会议。

从您的示例中,您有1000000个元素。如果不调用flush和clear,则将信息保存在所有1000000个元素的第一级缓存中。您可以在循环中每次新迭代时将逐个新实体添加到会话上下文中,但是在批准备/准备之后您不需要此信息,这就是您应该调用flush,clear - 删除不再需要的信息的原因。

答案 2 :(得分:0)

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

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