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
存储库来保存它们,而是让Job
entity 的存储库处理它。我使用 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 不进行批量插入