使用JPA和Hibernate进行批量处理的最佳方式

在编写企业应用程序时,通常将工作分配在服务于典型OLTP(在线事务处理)传输的前端系统,和一个或多个批处理用于ETL(Extract,Transform,Load)操作。

批处理器,顾名思义,将要处理的数据分成几个块,因此具有以下优点:

每个块可以通过单独的工作线程进行处理,因此增加了吞吐量并减少了整个处理时间。

每个块都可以使用自己的数据库事务,所以如果有一个失败,就不用丢掉我们所做的所有工作,只是针对当前事务的变化。

JPA 批处理

当使用JPA时,假设要插入50个Post实体,那么应该这样做:

int entityCount = 50; int batchSize = 25;   EntityManager entityManager = null; EntityTransaction transaction = null;   try {     entityManager = entityManagerFactory()         .createEntityManager();       transaction = entityManager.getTransaction();     transaction.begin();       for ( int i = 0; i < entityCount; ++i ) {         if ( i > 0 && i % batchSize == 0 ) {             entityManager.flush();             entityManager.clear();               transaction.commit();             transaction.begin();         }           Post post = new Post(             String.format( "Post %d", i + 1 )         );         entityManager.persist( post );     }       transaction.commit(); } catch (RuntimeException e) {     if ( transaction != null &&          transaction.isActive()) {         transaction.rollback();     }     throw e; } finally {     if (entityManager != null) {         entityManager.close();     } }

每个开始操作都会开启事务,因为每个实体状态转换必须在数据库事务的范围内执行。

for循环一次会持久化一个Post实体。 但是,由于实体状态转换仅在执行flush方法更新数据库时执行,因此我们可以将多个SQL 插入语句分组到到要给单一PreparedStatement执行中,该执行需要多个参数。

每次迭代计数器(变量i)已达到batchSize临界值的倍数,我们可以刷新EntityManager并提交数据库事务。 通过在每次批处理执行后提交数据库事务,我们获得以下优点:

避免了长期运行的事务,这对MVCC关系数据库系统是不利的。

我们确保如果执行失败,不会丢失以前成功执行的批处理作业完成的工作。

EntityManager在每次批量执行后被清除,这样就不会继续累积可能导致管理实体的几个问题:

如果要持久化的实体数量庞大,那么存在内存不足的风险。

在持久化上下文中累积的实体越多,flush越慢。 所以,最好的做法是确保持久性上下文尽可能的短小。

如果抛出异常,我们必须确保回滚当前正在运行的数据库事务。 否则可能会导致许多问题,因为数据库可能仍然认为事务处于打开状态,锁可能会被持有,直到事务超时或由DBA结束。

最后,我们需要关闭EntityManager,以便可以清除上下文并释放Session级的资源。

虽然这是使用JPA进行批处理的正确方法,但还没有完成。 如前所述,也可以从JDBC批量更新中受益。 为此,我们需要提供以下Hibernate配置属性:

<property     name="hibernate.jdbc.batch_size"     value="25" />   <property     name="hibernate.order_inserts"      value="true" />   <property     name="hibernate.order_updates"      value="true" />

这些属性允许我们将多个SQL语句批处理为单个PreparedStatement执行,这需要单个数据库往返。 选择值25以匹配EntityManager批处理作业的临界值。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/96c80de6cdedc7d31711c530c31bd3c4.html