休眠:为什么即使我在配置文件中设置了batch_size,我也要手动刷新()



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

在配置中指定 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的标识符策略。

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

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

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

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

"我为什么要手动执行 flush() 和 clear()?这不是应该由休眠自动完成的事情吗,因为" - 主要是,休眠在提交时这样做。方法flush()和clear()与使用batch_size无关,无论您是否有批处理模式,您都可以调用它们。

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

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

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

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

最新更新