1. 概述

本文将介绍如何使用 Hibernate/JPA 实现批量插入和更新操作。

批量操作的核心在于:将多个 SQL 语句通过一次网络请求发送给数据库,从而减少网络开销,提高性能。对于高并发或数据量大的系统,合理使用批量操作可以显著提升系统吞吐量。


2. 环境准备

2.1 示例数据模型

我们使用两个实体类来演示批量操作:SchoolStudent

@Entity
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String name;

    @OneToMany(mappedBy = "school")
    private List<Student> students;

    // Getters and setters...
}
@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String name;

    @ManyToOne
    private School school;

    // Getters and setters...
}

2.2 SQL 日志追踪

为了验证是否真正进行了批量操作,我们使用数据源代理来追踪 SQL 语句:

private static class ProxyDataSourceInterceptor implements MethodInterceptor {
    private final DataSource dataSource;
    public ProxyDataSourceInterceptor(final DataSource dataSource) {
        this.dataSource = ProxyDataSourceBuilder.create(dataSource)
            .name("Batch-Insert-Logger")
            .asJson().countQuery().logQueryToSysOut().build();
    }

    // 其他方法...
}

3. 默认行为

Hibernate 默认 不启用批量操作。这意味着每次插入或更新操作都会单独发送 SQL 语句。

示例代码如下:

@Transactional
@Test
public void whenNotConfigured_ThenSendsInsertsSeparately() {
    for (int i = 0; i < 10; i++) {
        School school = createSchool(i);
        entityManager.persist(school);
    }
    entityManager.flush();
}

日志显示每个插入语句都是独立执行的:

"querySize":1, "batchSize":0, "query":["insert into school (name, id) values (?, ?)"], 
  "params":[["School1","1"]]
...

要启用批量操作,必须设置以下配置:

hibernate.jdbc.batch_size=5

如果是 Spring Boot 项目,配置如下:

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

4. 单表批量插入

4.1 无显式 flush 的批量插入

当插入单一类型实体时,启用批量配置后,Hibernate 会自动进行批量插入。

示例代码如下:

@Transactional
@Test
public void whenInsertingSingleTypeOfEntity_thenCreatesSingleBatch() {
    for (int i = 0; i < 10; i++) {
        School school = createSchool(i);
        entityManager.persist(school);
    }
}

日志显示插入语句被合并为两个批次,每批 5 条:

"batch":true, "querySize":1, "batchSize":5, "query":["insert into school (name, id) values (?, ?)"], 
  "params":[["School1","1"],["School2","2"],["School3","3"],["School4","4"],["School5","5"]]
"batch":true, "querySize":1, "batchSize":5, "query":["insert into school (name, id) values (?, ?)"], 
  "params":[["School6","6"],["School7","7"],["School8","8"],["School9","9"],["School10","10"]]

⚠️ 注意:Hibernate 会将插入的实体缓存在 Persistence Context 中,插入大量数据时可能导致内存溢出(OutOfMemoryError)。

4.2 显式 flush 和 clear

为了减少内存占用,可以在每批插入后执行 flush()clear()

@Transactional
@Test
public void whenFlushingAfterBatch_ThenClearsMemory() {
    for (int i = 0; i < 10; i++) {
        if (i > 0 && i % BATCH_SIZE == 0) {
            entityManager.flush();
            entityManager.clear();
        }
        School school = createSchool(i);
        entityManager.persist(school);
    }
}

✅ 这样做可以避免内存中缓存过多实体,同时不影响批量插入效果。


5. 多表批量插入

5.1 多实体插入问题

当插入多个不同类型的实体时,Hibernate 默认不会跨类型合并批次,而是为每个类型创建新批次。

示例代码如下:

@Transactional
@Test
public void whenThereAreMultipleEntities_ThenCreatesNewBatch() {
    for (int i = 0; i < 10; i++) {
        if (i > 0 && i % BATCH_SIZE == 0) {
            entityManager.flush();
            entityManager.clear();
        }
        School school = createSchool(i);
        entityManager.persist(school);
        Student firstStudent = createStudent(school);
        Student secondStudent = createStudent(school);
        entityManager.persist(firstStudent);
        entityManager.persist(secondStudent);
    }
}

日志显示插入 SchoolStudent 时批次大小不理想:

"batch":true, "querySize":1, "batchSize":1, "query":["insert into school ..."]
"batch":true, "querySize":1, "batchSize":2, "query":["insert into student ..."]
...

❌ 这种方式效率较低,无法充分利用批量插入的优势。

5.2 优化:启用 hibernate.order_inserts

为了优化多表插入行为,需启用以下配置:

hibernate.order_inserts=true

Spring Boot 配置如下:

spring.jpa.properties.hibernate.order_inserts=true

启用后,Hibernate 会先收集所有插入语句,再按实体类型进行排序并批量发送。

优化后日志如下:

"batch":true, "querySize":1, "batchSize":5, "query":["insert into school ..."]
"batch":true, "querySize":1, "batchSize":5, "query":["insert into student ..."]

✅ 插入效率大幅提升。


6. 批量更新

Hibernate 同样支持批量更新操作,但需要启用两个配置项:

hibernate.order_updates=true
hibernate.batch_versioned_data=true

Spring Boot 配置如下:

spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.batch_versioned_data=true

示例代码如下:

@Transactional
@Test
public void whenUpdatingEntities_thenCreatesBatch() {
    TypedQuery<School> schoolQuery = 
      entityManager.createQuery("SELECT s from School s", School.class);
    List<School> allSchools = schoolQuery.getResultList();
    for (School school : allSchools) {
        school.setName("Updated_" + school.getName());
    }
}

日志显示更新语句被合并为两个批次,每批 5 条:

"batch":true, "querySize":1, "batchSize":5, "query":["update school set name=? where id=?"], 
  "params":[["Updated_School1","1"],["Updated_School2","2"],...]

✅ 批量更新同样可以显著减少数据库交互次数,提升性能。


7. 主键生成策略影响

Hibernate 的批量插入功能对主键生成策略有要求:

  • 支持批量操作的主键策略GenerationType.SEQUENCEGenerationType.TABLE
  • 不支持批量操作的主键策略GenerationType.IDENTITY

如果使用 IDENTITY,Hibernate 会自动禁用批量插入,即使配置了 hibernate.jdbc.batch_size 也不会生效。

所以,建议使用 SEQUENCE 作为主键生成策略

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;

8. 总结

本文介绍了如何在 Hibernate/JPA 中启用并优化批量插入和更新操作:

关键配置点

  • hibernate.jdbc.batch_size:启用批量操作
  • hibernate.order_inserts:优化多表插入顺序
  • hibernate.order_updates + hibernate.batch_versioned_data:启用批量更新
  • 主键策略使用 SEQUENCE 而非 IDENTITY

优化建议

  • 插入大量数据时,定期调用 flush()clear() 减少内存占用
  • 使用数据源代理工具(如 ProxyDataSource)验证是否真正执行了批量操作

通过合理配置和使用,Hibernate 的批量操作可以显著提升数据处理性能。


原始标题:Batch Insert/Update with Hibernate/JPA | Baeldung