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

798qvoo8  于 6个月前  发布在  其他
关注(0)|答案(3)|浏览(74)

我正在学习使用java的hibernate5.2.10,我从网上的一些教程开始,但面临着以下问题。
在使用hibernate.jdbc.batch_size时,我见过的所有教程都是先在配置文件中设置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的值呢?

enxuqcxy

enxuqcxy1#

在配置中指定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个网络包。
现在重要的是要认识到,有一些因素可以 * 禁用 *,例如使用基于身份的标识符,如IDENTITYAUTO_INCREMENT。为什么?
这是因为为了让Hibernate将实体存储在PersistenceContext中,它必须知道实体的ID,而在使用基于IDENTITY的标识符生成时,获取该值的唯一方法是在每次插入操作后实际查询数据库以获取该值。因此,插入不能批量进行。
这正是为什么执行批量插入操作的人经常观察到性能不佳的原因,因为他们没有意识到他们选择的标识符生成策略可能产生的影响。
当您希望优化批加载时,最好使用某种类型的缓存序列生成器或一些手动应用程序分配的标识符。
现在回到使用flush()clear()的示例,标识符生成策略也存在同样的问题。如果您希望将这些操作批量/批量发送到数据库,请注意您为Student使用的标识符策略。

bpzcxfmw

bpzcxfmw2#

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

字符串
你应该调用flush()方法来强制生成sql查询并执行它们。如果你不手动调用flush(),如果由hibernate调用并提交事务时间。
你应该调用clear()方法从持久化上下文中删除有关实体的信息,以避免OutOffMemoryException,因为你可能有一个包含大量实体的批处理,它们可能会消耗大量内存。
你应该手动控制批处理操作,因为不是所有的hibernate操作都需要批处理模式。
“为什么我应该手动执行flush()和clear()?这不是应该由hibernate自动完成的吗?“--主要是,hibernate在提交时执行。方法flush()和clear()独立于使用batch_size,无论是否使用批处理模式,都可以调用它们。
你可能会遇到这样一种情况,当你在dao方法内部调用N次flush()时-当你需要实体和数据库级别之间的同步时,调用flush()-当你不再使用实体时,想要清理会话。
从你的例子中,你有1000000个元素。如果不调用flush和clear,你会把所有1000000个元素的信息保存在一级缓存中。你会在循环中的每个新迭代中一个接一个地把新实体添加到会话上下文中,但是在批处理就绪/准备好之后,你就不需要这些信息了,这就是为什么你应该调用flush,clear -删除你不再需要的信息。

sf6xfgos

sf6xfgos3#

回答你在描述中提出的问题,正如我所研究的,flush()-ing批处理/事务与commit()-ing事务不同。
每隔50个块刷新一次事务,这意味着您正在将事务作为50个块的批同步到数据库。50个块已与数据库同步,但尚未提交。
但是当你在配置文件中定义batch-size时,你是在告诉Hibernatecommit40的batch(假设你在conf文件中设置了batch size 40)。

相关问题