我有一个测试用例,需要将100‘000个实体实例持久存储到数据库中。我当前使用的代码可以做到这一点,但是要花40秒才能将所有数据持久保存在数据库中。从大小约为15 MB的JSON文件中读取数据。
现在,在另一个项目之前,我已经在自定义存储库中实现了批量插入方法。但是,在那种情况下,我要保留许多顶级实体,而只有少数嵌套实体。
在我目前的情况下,我有5个Job实体,其中包含约30个JobDetail实体的列表。其中一个JobDetail包含850至1100个JobEnvelope实体。
Job
JobDetail
JobEnvelope
当写入数据库时,我Job使用默认save(Iterable<Job> jobs)接口方法提交实体列表。所有嵌套实体均具有CascadeType PERSIST。每个实体都有自己的表。
save(Iterable<Job> jobs)
PERSIST
启用批处理插入的通常方法是实现一个自定义方法saveBatch,该方法每隔一段时间刷新一次。但是在这种情况下,我的问题是JobEnvelope实体。我不使用JobEnvelope存储库来持久化它们,而是让Job实体的存储库来处理它。我正在使用MariaDB作为数据库服务器。
saveBatch
因此,我的问题可以归结为以下几点:如何将JobRepository插入的嵌套实体批量化?
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; }
确保正确配置与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对update语句具有相同的作用)。
hibernate.order_inserts
hibernate.order_updates
jdbc.batch_size是Hibernate将使用的最大批处理大小。尝试分析不同的值,然后选择一个在您的用例中表现出最佳性能的值。
jdbc.batch_size
请注意,如果使用id生成器,则将禁用插入语句的批处理IDENTITY。
IDENTITY
特定于MySQL,您必须指定rewriteBatchedStatements=true作为连接URL的一部分。为确保批处理按预期方式工作,请添加profileSQL=true以检查驱动程序发送到数据库的SQL。
rewriteBatchedStatements=true
profileSQL=true
如果您的实体已版本化(出于乐观锁定目的),那么为了利用批量更新(不影响插入),您还必须打开:
<property name="hibernate.jdbc.batch_versioned_data">true</property>
使用此属性,您可以告诉Hibernate在执行批处理更新(需要执行版本检查)时JDBC驱动程序能够返回受影响行的正确计数。您必须检查这对于您的数据库/ jdbc驱动程序是否正常工作。例如,它不适用于Oracle11和更早的Oracle版本。
您可能还希望在每个批处理之后刷新并清除持久性上下文以释放内存,否则所有托管对象都将保留在持久性上下文中,直到将其关闭。
另外,您可能会发现此博客很有用,因为它很好地解释了Hibernate批处理机制的详细信息。