Spring Data JPA:嵌套实体的批量插入

Posted

技术标签:

【中文标题】Spring Data JPA:嵌套实体的批量插入【英文标题】:Spring Data JPA: Batch insert for nested entities 【发布时间】:2016-06-17 21:54:21 【问题描述】:

我有一个测试用例,我需要将 100'000 个实体实例持久化到数据库中。我目前使用的代码就是这样做的,但是在所有数据都保存在数据库中之前最多需要 40 秒。数据是从一个大小约为 15 MB 的 JSON 文件中读取的。

现在我已经在自定义存储库中为另一个项目实现了批量插入方法。但是,在那种情况下,我有很多***实体要持久化,只有几个嵌套实体。

在我目前的情况下,我有 5 个 Job 实体,其中包含大约 30 个 JobDetail 实体的列表。一个 JobDetail 包含 850 到 1100 个 JobEnvelope 实体。

写入数据库时​​,我使用默认的save(Iterable<Job> jobs) 接口方法提交Job 实体列表。所有嵌套实体都有 CascadeType PERSIST。每个实体都有自己的表格。

启用批量插入的常用方法是实现一个自定义方法,如saveBatch,每隔一段时间刷新一次。但在这种情况下,我的问题是JobEnvelope 实体。我不使用JobEnvelope 存储库来保存它们,而是让Jobentity 的存储库处理它。我使用 MariaDB 作为数据库服务器。

所以我的问题归结为以下几点:如何使JobRepository 批量插入它的嵌套实体?

这些是我的 3 个有问题的实体:

工作

@Entity
public class Job 
  @Id
  @GeneratedValue
  private int jobId;

  @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "job")
  @JsonManagedReference
  private Collection<JobDetail> jobDetails;

工作详情

@Entity
public class JobDetail 
  @Id
  @GeneratedValue
  private int jobDetailId;

  @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
  @JoinColumn(name = "jobId")
  @JsonBackReference
  private Job job;

  @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "jobDetail")
  @JsonManagedReference
  private List<JobEnvelope> jobEnvelopes;

工作信封

@Entity
public class JobEnvelope 
  @Id
  @GeneratedValue
  private int jobEnvelopeId;

  @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
  @JoinColumn(name = "jobDetailId")
  private JobDetail jobDetail;

  private double weight;

【问题讨论】:

【参考方案1】:

完成 Dragan Bozanovic 之前的回答。 Hibernate 有时会静默地停用批次的执行顺序,例如,如果它在构建批次之间的依赖关系图时遇到实体之间的循环关系(请参阅 InsertActionSorter.sort(..) 方法)。当这种情况发生时,hibernate 跟踪这种行为会很有趣。

【讨论】:

【参考方案2】:

确保正确配置 Hibernate 批处理相关的属性:

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

关键是如果连续的语句操作同一个表,它们可以被批处理。如果出现插入另一个表的语句,则必须在该语句之前中断并执行先前的批处理构造。使用 hibernate.order_inserts 属性,您将允许 Hibernate 在构造批处理语句之前重新排序插入(hibernate.order_updates 对更新语句具有相同的效果)。

jdbc.batch_size 是 Hibernate 将使用的最大批量大小。尝试分析不同的值,然后选择一个在您的用例中表现出最佳性能的值。

请注意,如果使用IDENTITY id 生成器,则插入语句的批处理为disabled。

特定于 mysql,您必须指定 rewriteBatchedStatements=true 作为连接 URL 的一部分。要确保批处理按预期工作,请添加 profileSQL=true 以检查驱动程序发送到数据库的 SQL。更多详情here.

如果您的实体是版本化的(出于乐观锁定目的),那么为了利用批量更新(不影响插入),您还必须打开:

<property name="hibernate.jdbc.batch_versioned_data">true</property>

通过该属性,您可以告诉 Hibernate,JDBC 驱动程序能够在执行批量更新时返回正确的受影响行数(需要执行版本检查)。您必须检查这是否适用于您的数据库/jdbc 驱动程序。例如,它在 Oracle 11 和更早的 Oracle 版本中为 does not work。

您可能还想刷新和清除持久性上下文after each batch 以释放内存,否则所有托管对象都将保留在持久性上下文中,直到它被关闭。

另外,您可能会发现this blog 很有用,因为它很好地解释了 Hibernate 批处理机制的细节。

【讨论】:

非常感谢您的详细回复。所以基本不可能对使用@GeneratedValue注解的实体做批量插入? 可以,只是IDENTITY id生成器不行。适用于任何其他 id 生成器。 啊,我明白了。它设置为AUTO,MySQL 不支持SEQUENCE,所以我目前正在研究TABLE 代。猜测自动模式选择了IDENTITY 方法,因为没有序列表并且不支持另一个表。会回来报告的。 很有可能,因为native是默认的,我想如果你只指定@GeneratedValue,它首先检查数据库是否支持IDENTITY 天哪,非常感谢 - 确实创造了奇迹。插入这 100'000 个条目只需要 5 秒而不是 40 秒 :)

以上是关于Spring Data JPA:嵌套实体的批量插入的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data JPA HIbernate 批量插入速度较慢

Spring Data JPA saveAll 不进行批量插入

使用 Spring Boot 和 Spring Data JPA 批量插入不起作用

Spring data jpa嵌套事务回滚不删除插入?

Spring Data JPA - 并发批量插入/更新

如何为批量插入配置spring boot和data jpa