1. 概述

在本篇文章中,我们将深入探讨 Spring Data 中 save()saveAll() 方法在性能上的差异。

对于日常开发中频繁操作数据库的场景,选择合适的数据持久化方法对系统性能至关重要。虽然这两个方法看起来功能相似,但在批量操作场景下,它们的表现差异可能非常显著。

2. 应用准备

为了进行性能对比测试,我们首先需要一个 Spring 应用,包含一个实体类和对应的 Repository 接口。

2.1 实体类定义

我们创建一个简单的 Book 实体类:

@Entity
public class Book {

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

    private String title;
    private String author;

    // 构造函数、getter 和 setter 省略
}

2.2 Repository 接口定义

public interface BookRepository extends JpaRepository<Book, Long> {
}

3. 性能测试

我们通过保存 10,000 条 Book 数据来测试 save()saveAll() 的性能表现。

3.1 使用 save() 方法逐条保存

for(int i = 0; i < bookCount; i++) {
    bookRepository.save(new Book("Book " + i, "Author " + i));
}

3.2 使用 saveAll() 方法批量保存

List<Book> bookList = new ArrayList<>();
for (int i = 0; i < bookCount; i++) {
    bookList.add(new Book("Book " + i, "Author " + i));
}

bookRepository.saveAll(bookList);

3.3 测试结果

在我们的测试中:

  • 使用 save() 方法耗时约 2 秒
  • 使用 saveAll() 方法耗时约 0.3 秒

结论:saveAll() 的性能明显优于 save()

此外,当我们启用 JPA 批量插入(JPA Batch Inserts)后:

  • save() 方法性能 下降约 10%
  • saveAll() 方法性能 提升约 60%

⚠️ 踩坑提示:不要小看事务与批量操作之间的关系,它对性能的影响可能超出你的预期。

4. 差异分析

虽然从表面上看,saveAll() 内部是遍历调用 save(),但实际性能差异来源于 事务管理机制

4.1 事务注解分析

查看源码可以发现,两个方法都被 @Transactional 注解修饰。

默认的事务传播行为是 REQUIRED,即:

如果当前没有事务,则创建一个新事务;如果已有事务,则加入当前事务。

4.2 实际事务行为差异

  • save()每次调用都开启一个新事务(除非已有事务)
  • saveAll()只开启一个事务,并在该事务中调用多次 save()

因此,saveAll() 避免了频繁开启事务的开销。

4.3 批量插入与事务的关系

当开启 JPA 批量插入时,批量操作是在事务提交时生效的。因此:

  • save() 多次开启事务 → 批量插入无法有效合并
  • saveAll() 单事务 → 批量插入能完整执行,性能提升显著

误区提醒:不要认为 saveAll() 只是 save() 的循环封装,事务上下文才是性能差异的关键。

5. 总结

本文通过实际测试和源码分析,揭示了 Spring Data 中 save()saveAll() 的性能差异:

方法 事务行为 批量插入效果 推荐使用场景
save() 每次新建事务 单条插入
saveAll() 复用单个事务 批量插入 ✅

在实际开发中,**批量操作务必优先考虑 saveAll()**,特别是在开启 JPA 批量插入的情况下,性能提升尤为明显。

如需查看本文示例代码,可访问 GitHub 仓库: https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-jpa-repo-2


原始标题:Performance Difference Between save() and saveAll() in Spring Data | Baeldung