6

我有一个@Entity包含一些@OneToMany关系,但由于它们由Enums 的集合组成,我正在使用@ElementCollection. 实体具有在数据库级别(MySQL)生成的 id。

这是我刚刚编写的一个与我的实体结构相对应的小示例。

@Entity
public class Student {

  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  @ElementCollection(targetClass = Language.class)
  @CollectionTable(name="student_languages", joinColumns=@JoinColumn(name="student_id"))
  private Set<Language> languages;

  @ElementCollection(targetClass = Module.class)
  @CollectionTable(name="student_modules", joinColumns=@JoinColumn(name="student_id"))
  private Set<Module> modules;

  @ElementCollection(targetClass = SeatPreference.class)
  @CollectionTable(name="student_seats", joinColumns=@JoinColumn(name="student_id"))
  private Set<SeatPreference> seatPreference;

[...]
}

我知道这会GenerationType.IDENTITY停用批处理,但我认为这仅适用于主要实体,而不适用于单个属性。我不得不批量导入一些实体(~20k),每个实体都有一些属性,但是 Hibernate 似乎为集合中的每个属性生成一个插入,使得导入速度非常慢(每个插入 10 到 20 个)记录)。

我现在花了很长时间试图让它更快,我正在考虑只生成一个可以手动导入数据库的 SQL 文件。

有没有办法指示 Hibernate 批量插入@ElementCollection字段?难道我做错了什么?

4

2 回答 2

2

基本上,似乎休眠对批处理没有帮助,@ElementCollection 但您可以使用 SQL批量插入。似乎您在支持批量插入的 MySQL 上,如果您启用该 rewriteBatchedStatements属性,它的 JDBC 驱动程序可以自动将各个插入语句修改/重写为一个批量语句。

因此,在您的情况下,您需要做的是告诉休眠启用批处理并订购批处理插入和更新。

hibernate.jdbc.batch_size=100
hibernate.order_inserts=true
hibernate.order_updates=true

这将确保在将数据插入 DB 时,由 Hibernate 生成的插入语句将批量执行并且它们将被排序。

所以 Hibernate 生成的 SQL 会是这样的:

insert into student_languages (student_id, languages) values (1,1)
insert into student_languages (student_id, languages) values (1,2)
insert into student_languages (student_id, languages) values (1,3)
insert into student_languages (student_id, languages) values (1,4)

接下来,您需要通过设置rewriteBatchedStatements=true

jdbc:mysql://db:3306/stack?useSSL=false&rewriteBatchedStatements=true

所以这将指示驱动程序将插入重写为批量形式,因此上面的几个SQL语句将被重写为这样的东西

insert into student_languages (student_id, languages) values (1,1),(1,2),(1,3),(1,4)

正如一个信息,如果您使用的是旧版本的 MySQL 驱动程序和 Hibernate,这可能不起作用。

于 2022-01-05T08:59:04.020 回答
0

我用 MySQL 和 MariaDB 对此进行了测试,实际上 Hibernate 将批量插入到集合表中。但是肉眼是看不到的,必须使用DataSource-Proxy才能看到:

INFO  com.example.jpa.AddStudents - Adding students
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener - 
Name:dataSource, Connection:3, Time:1, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into student (name) values (?)"]
Params:[(Smith)]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener - 
Name:dataSource, Connection:3, Time:1, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["insert into student (name) values (?)"]
Params:[(Snow)]
DEBUG n.t.d.l.l.SLF4JQueryLoggingListener - 
Name:dataSource, Connection:3, Time:78, Success:True
Type:Prepared, Batch:True, QuerySize:1, BatchSize:6
Query:["insert into student_languages (student_id, language) values (?, ?)"]
Params:[(6,2),(6,0),(6,1),(7,0),(7,4),(7,3)]
INFO  com.example.jpa.AddStudents - Added

SEQUENCEID 生成器被认为是 Hibernate的最佳选择。它不会像TABLE生成器那样创建锁争用并允许批处理。不幸的是 MySQL 仍然不支持序列(MariaDB 支持)。

难道我做错了什么?

Hibernate 针对数据库中的小规模更改进行了优化。它维护一级缓存,也支持二级缓存,这只会影响大规模操作的性能。因此,确实,您最好按照评论中的建议使用 JDBC 或 jOOQ 进行此特定操作。


我使用了 MySQL 8.0.3、MariaDB 10.5.13 和 Hibernate 5.6.3.Final。

于 2022-01-05T18:13:33.780 回答