Hibernate 4 批处理

原文: https://javabeginnerstutorial.com/hibernate/batch-execution-with-hibernate-4/

在本文中,我将为您简要介绍 Hibernate 批处理。

为什么要批处理?

因为这比打开一个事务向数据库插入 1000000(一百万)个条目并最后提交或为每个相同容量的插入打开事务要好。

批处理为您提供了管理此工具的正确工具:在 Hibernate 自动调用commit之后定义一个限制,以将您的数据持久存储在应用后面的数据库中。

朴素的方法

  1. public static void naiveApproach() {
  2. final Configuration configuration = new Configuration().configure();
  3. final StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
  4. final SessionFactory sessionFactory = configuration.buildSessionFactory(builder.build());
  5. final Session session = sessionFactory.openSession();
  6. final Transaction tx = session.beginTransaction();
  7. for (int i = 0; i < 1_000_000; i++) {
  8. final Book book = new Book("9781617291999", "Java 8 in Action", new Date());
  9. session.save(book);
  10. }
  11. tx.commit();
  12. session.close();
  13. }

上面的代码做了我一开始所描述的:它打开一个事务并在插入 1000000 条记录后保存。 根据您的硬件,您可以到达循环的结尾并在结尾处调用一次提交。 如果您的内存不足,您的应用可能会抛出OutOfMemoryException,因为 Hibernate 将所有新的Book实例存储在内存中的第二级缓存中。

为了进行测试,我将插入次数增加到 10000000(一千万),以查看应用崩溃之前需要花费多长时间。 凭借近三百万本新书,我达到了应用的 2GB 内存。

这意味着,如果二级缓存消耗了应用可用的所有内存,则数据库条目将消失,您可以重新开始。

设置批处理大小,将二级缓存保持在较低水平

为了使二级缓存的大小保持较低,您可以在hibernate.cfg.xml中引入批处理大小。 这将告诉 Hibernate 容器每隔 n 行要批量插入。 可以使用属性hibernate.jdbc.batch_size设置批处理大小。

有趣的是,Hibernate 的文档不仅引入了此属性,而且还需要修改上面的朴素代码(我只会复制相关的for循环):

  1. for (int i = 0; i < 1_000_000; i++) {
  2. final Book book = new Book("9781617291999", "Java 8 in Action", new Date());
  3. session.save(book);
  4. if(i % 50 == 0) { // 50 is the batch_size
  5. session.flush();
  6. session.clear();
  7. }
  8. }

如果仔细看一下上面的代码,您会看到解决方案是将批处理大小大块中的会话flush()clear()保持为较低的二级缓存大小。

那么为什么要设置批处理大小?

很好的问题,我已经搜索了 Hibernate 的文档以找到有关此信息,但未找到任何信息。 但是,如果考虑到这一点,批处理可以通过将一堆语句组合在一起,从而使数据库有效地执行插入和更新语句。

批次大小无法保证

设置批处理限制并不能保证仅由于将数据刷新到数据库而使第二级缓存大小保持较小。

但是,还有一些透明的限制,您最终将看不到。

一个示例是如果您使用GenerationType.IDENTITY,则 Hibernate 透明地禁用批处理。

第二个示例是 Hibernate 查看要一起批处理的语句:如果当前语句与前一个相同,则在未达到batch_size的情况下将它们合并在一起。 在上面的示例中,语句相似,因此将它们批处理在一起。 但是,如果我们将“作者”和“书籍”一起添加到数据库中,则 Hibernate 会看到交替的语句,并且将从每个语句开始一个批处理组。 要解决此问题,可以使用hibernate.order_insertshibernate.order_updates属性。 这使 Hibernate 在插入之前对语句进行了排序,因此可以看到 50 个Book插入可以一起批处理,并且 50 个Authors可以一起批处理。

手动保存数据

我们已经解决了消耗大量内存的问题,但是插入时的异常又如何呢? 在大多数情况下,由于最后一个失败而回滚一百万次插入是不可行的。

解决方案是手动调用事务的提交以及批处理大小:

  1. for (int i = 0; i < 1_000_000; i++) {
  2. final Book book = new Book("9781617291999", "Java 8 in Action", new Date());
  3. session.save(book);
  4. if(i % 50 == 0) { // 50 is the batch_size
  5. session.flush();
  6. session.clear();
  7. session.getTransaction().commit();
  8. session.beginTransaction();
  9. }
  10. }

如果我们达到了batch_size,则上面的代码将提交事务,并在会话上开始新事务,因为前一个事务已因提交而失效。

总结

批处理仅有助于将数据高效地存储在数据库中。 如果要使用某种故障转移机制,则需要实现手动提交策略。

代码下载