有没有办法跟踪或获取 JPA 在由于 BatchUpdateException 而失败之前完成的批迭代总数?

Posted

技术标签:

【中文标题】有没有办法跟踪或获取 JPA 在由于 BatchUpdateException 而失败之前完成的批迭代总数?【英文标题】:Is there a way to track or get the total number of batch iterations JPA completes before failing due to BatchUpdateException? 【发布时间】:2019-08-09 02:10:18 【问题描述】:

我需要使用 Spring-JPA (Hibernate) 持久化 N 个实体,并且我已将我的 spring 批量大小设置为 M,其中 M

我会将所有 N 个实体提交到存储库,它遵循以下逻辑

entities.forEach(entity->entityManager.persist(entity));
entityManager.flush();

整个操作由@Transactional 包裹。

基于https://vladmihalcea.com/how-to-find-which-statement-failed-in-a-jdbc-batch-update,它给了我更好的结果,但挑战在于,BatchUpdateException.getUpdateCounts() 给出了每个批处理操作中持久化的总数,但不包括失败前的所有内部迭代的总数。

例如,如果我需要持久化 100 个实体,spring 批量大小 = 5

spring.jpa.properties.hibernate.jdbc.batch_size=5

13 条记录是导致失败的坏记录。 BatchUpdateException.getUpdateCounts() 返回 2,这是因为它在批处理循环的第 3 次迭代中失败。相反,我想获得 12 次成功插入的计数。是否有任何 API 或某种方式来跟踪这个,而不是在外部跟踪,(这会破坏我的目的,通过多次调用 flush)

AtomicInteger ai = new AtomicInteger(0);
entities.forEach(entity-> entityManager.persist(entity); 
                           ai.getAndIncrement();
                           if(ai.get() % batchsize)
                               entityManager.flush();
                           );
entityManager.flush();

谢谢

【问题讨论】:

请提供使用的数据库和Hibernate方言的信息。 使用的数据库:Oracle 12C 和 Hibernate 方言:org.hibernate.dialect.Oracle12cDialect 【参考方案1】:

有几条关于使用 Hibernate 批量插入到 Oracle 12 的新闻。先说好。

Hibernate Oracle 12 批量插入

如果设置属性,Hibernate(至少在我测试的 5.4.4 版本中)确实支持批量插入

 <property name="hibernate.jdbc.batch_size" value="3"/>

识别它有点棘手,因为 Hibernate 日志记录与正常模式日志记录没有区别。可能是由于 Oracle 没有将值集合传递给 INSERT 的语法,您会看到单个插入语句的日志

 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)

但通过检查 Oracle 10046 跟踪,您可以看到每次执行 INSERT 游标都会处理 batch_size 行(请参阅 EXEC 跟踪行中的参数 r=3 - 批大小设置为 3)

 PARSING IN CURSOR #347407728 ..
 insert into AUTHOR (name, AUTHOR_ID) values (:1 , :2 )
 END OF STMT 

 EXEC #347407728:....,r=3,...

请注意,很遗憾,您不能在批处理模式下将 IDENTITY 列用作主键

  AUTHOR_ID INT  GENERATED ALWAYS AS IDENTITY PRIMARY KEY,

使用 IDENTITY 将关闭批处理模式

getUpdateCount

第二个好消息是,如果您在批处理中遇到异常,您可以获得当前批处理的 updateCounts - 您必须取消嵌套您使用此伪代码收到的 PersistenceException

 e.getCause().getSQLException().getUpdateCounts()

但请注意,您需要使用 Oracle 12 并使用相应的 JDBC 驱动程序才能查看确切的更新计数 - 在以前的版本中,您只会看到一个非特定错误(单个负数)。

把它们放在一起

因此,结合这两个功能,您可以 - 至少在理论上 - 识别失败的记录

batch_size =3 的示例

您会看到 6 个记录的行

 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)
 Hibernate: insert into AUTHOR (name, AUTHOR_ID) values (?, ?)

即开始了 2 批,第二批失败,成功处理了两行

 BatchUpdateException - update count: [1, 1]

这意味着 3 + 2 行正常,第 6 行失败

总结

您可能会争辩说,Hibernate 的人没有做功课,并且 阅读日志 不是识别问题的好方法。我对此没有异议,我只能提供一些见解,您可能会从 Hibernate 作者那里听到(请注意,除了对数据库问题进行异常故障排除外,我与 Hibernate 没有任何关系)。

验证输入

这当然值得商榷,但在使用批量输入时,您应该预先验证数据,以免出现异常。

每批次刷新

你反对它,但实际上它并没有真正的性能损失。每次刷新时,INSERT 游标都会关闭并重新打开,但由于 Oracle 游标兑现这没什么大不了的。

性能不是您的首要目标

最重要的是,在决定使用 Hibernate 进行批量数据输入时,性能绝对不是您的首要目标。您选择舒适的数据输入,并为此支付一些绩效税。

我的测试显示在大约 50 秒内存储 100K 个简单对象(批量大小为 1000)的经过时间。每个对象 0.4 毫秒的平均值还不错,但使用直接 SQL INSERT 处理 100K 行需要 2 秒以下。因此,对于单个步骤,例如迁移和升级具有极短的时间窗口,您可以从使用直接 JDBC 或事件 SQL 中获益。

【讨论】:

以上是关于有没有办法跟踪或获取 JPA 在由于 BatchUpdateException 而失败之前完成的批迭代总数?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法获取带有结果集的 JPA 命名查询的计数大小?

Android WebView:有没有办法获取 javascript 堆栈跟踪?

如何在Processing debugger中获取堆栈跟踪

有没有办法使用 SQL 或 EXCEL 跟踪两个表的更改?

当方法被意外调用的次数超过指定次数时,有没有办法从 rspec 获取堆栈跟踪?

如何获取 JPA 生成的 SQL 查询?