Spring Framework中JDBC批量操作的三种实现方式

 更新时间:2025年10月30日 09:06:31   作者:lang20150928  
本文详细介绍了如何使用 Spring 的 JdbcTemplate 进行高效的数据库批量更新,从而减少与数据库之间的网络往返次数(round-trips),提升性能,下面我将用通俗易懂的方式,结合代码示例和实际场景给大家详细说说,需要的朋友可以参考下

这篇文章详细介绍了如何使用 Spring 的 JdbcTemplate 进行高效的数据库批量更新,从而减少与数据库之间的网络往返次数(round-trips),提升性能。

下面我将用通俗易懂的方式,结合代码示例和实际场景,帮你系统地理解这一节的核心思想和三种主要批量处理方式。

为什么要用“批量操作”?

在没有批量处理时,如果你要插入或更新 1000 条数据:

for (Actor actor : actors) {
    jdbcTemplate.update("INSERT INTO t_actor ...", actor.getName(), ...);
}

这会向数据库发送 1000 次独立的 SQL 请求 → 1000 次网络通信 → 效率极低。

而使用 批量操作(Batch Operations),你可以把多个 SQL 操作“打包”成一组,一次性提交给数据库执行:

✅ 结果:

  • 减少网络开销
  • 提高吞吐量(可能提升几十倍)
  • 更高效地利用数据库资源

基本批量操作:使用 BatchPreparedStatementSetter

这是最传统、控制最精细的方式。

使用方法:

实现 BatchPreparedStatementSetter 接口的两个方法:

  1. getBatchSize():返回本次批量操作的总条数
  2. setValues(PreparedStatement ps, int i):为第 i 条记录设置参数

示例说明:

public int[] batchUpdate(final List<Actor> actors) {
    return this.jdbcTemplate.batchUpdate(
            "update t_actor set first_name = ?, last_name = ? where id = ?",
            new BatchPreparedStatementSetter() {
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    Actor actor = actors.get(i);
                    ps.setString(1, actor.getFirstName());
                    ps.setString(2, actor.getLastName());
                    ps.setLong(3, actor.getId().longValue());
                }
                public int getBatchSize() {
                    return actors.size(); // 整个列表作为一批
                }
            });
}

关键点:

  • 整个 actors 列表被当作一个“大批次”一次性提交。
  • 返回值是 int[],每个元素表示对应 SQL 语句影响的行数。
  • 适用于:数据量不大且能一次性加载到内存的情况。

特殊情况:流式输入(不确定总数)

如果数据来自文件、流或数据库游标,无法预知总数,可以使用:

InterruptibleBatchPreparedStatementSetter

它有一个额外方法:

boolean isBatchExhausted();

当你读完所有数据后返回 true,Spring 就停止继续添加语句。

更简洁的方式:用对象列表做批量操作

Spring 提供了更简洁的 API —— 不用手动实现接口,只需传入一个对象列表!

方式一:使用命名参数(Named Parameters) + NamedParameterJdbcTemplate

public int[] batchUpdate(List<Actor> actors) {
    return this.namedParameterJdbcTemplate.batchUpdate(
        "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
        SqlParameterSourceUtils.createBatch(actors) // 自动提取 getter
    );
}

优点:

  • 使用 :firstName 这种命名参数,可读性强
  • SqlParameterSourceUtils.createBatch(actors) 自动从 Actor 对象的 getter 提取字段
  • 支持 POJO(JavaBean)、Map 等格式

要求:Actor 类必须有 getFirstName()、getId() 等标准 getter 方法

方式二:使用 ? 占位符 + List<Object[]>

List<Object[]> batch = new ArrayList<>();
for (Actor actor : actors) {
    Object[] values = new Object[] {
        actor.getFirstName(), 
        actor.getLastName(), 
        actor.getId()
    };
    batch.add(values);
}

jdbcTemplate.batchUpdate(
    "update t_actor set first_name = ?, last_name = ? where id = ?", 
    batch
);

优点:

  • 简单直观
  • 不依赖命名参数

注意事项:

  • 参数顺序必须严格匹配 SQL 中的 ? 顺序
  • 如果值为 null,Spring 需要通过反射推断类型(可能影响性能)

性能提示:

Spring 默认调用 ParameterMetaData.getParameterType() 来判断 null 值的类型,但某些数据库驱动(如 Oracle 12c)这个操作很慢。
可以通过设置系统属性关闭:

# 在 JVM 启动参数或 spring.properties 中添加
spring.jdbc.getParameterType.ignore=true

或者显式指定类型(更推荐)。

处理超大数据集:分多个小批次提交(Multiple Batches)

前面的例子都是一次性提交全部数据。但如果数据量太大(比如 10 万条),一次性加载到内存会导致内存溢出(OOM)。

解决方案:

使用分批提交(chunking),每 100 条提交一次。

public int[][] batchUpdate(final Collection<Actor> actors) {
    int[][] updateCounts = jdbcTemplate.batchUpdate(
        "update t_actor set first_name = ?, last_name = ? where id = ?",
        actors,              // 所有数据
        100,                 // 每批最多 100 条
        (ps, actor) -> {     // Lambda 设置参数
            ps.setString(1, actor.getFirstName());
            ps.setString(2, actor.getLastName());
            ps.setLong(3, actor.getId().longValue());
        }
    );
    return updateCounts;
}

返回值解释:

int[][] updateCounts
  • 第一层数组:每个“批次”的结果
  • 第二层数组:该批次中每条 SQL 的影响行数

例如:

[
  [1, 1, 1, ..., 1],  // 第1批,共100条,每条影响1行
  [1, 1, 1, ..., 1],  // 第2批
  [1, 1, 1]           // 最后一批只有3条
]

适用场景:

  • 处理大量数据(数万、数十万条)
  • 防止内存溢出
  • 支持进度监控、错误恢复

三种方式对比总结

方式适用场景优点缺点
BatchPreparedStatementSetter需要精确控制每条记录参数灵活、支持中断写法较繁琐
List<Object[]>SqlParameterSourceUtils.createBatch()数据已在内存中,结构简单写法简洁不适合超大数据集
分批提交(multiple batches)超大数据集内存友好、可控性强稍复杂

性能建议

合理设置批大小(batch size)

  • 太小:仍有多次往返
  • 太大:内存压力大,事务过长
  • 推荐值:50 ~ 1000(根据测试调整)

开启数据库的批量支持

  • MySQL:确保连接 URL 包含 rewriteBatchedStatements=true
jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
  • Oracle:原生支持较好
  • PostgreSQL:需要驱动支持(reWriteBatchedInserts=true

使用连接池(如 HikariCP)

  • 批量操作期间保持连接稳定

避免自动提交(Auto-commit)

  • 在事务中执行批量操作,防止中途失败导致部分写入

实际应用建议(Spring Boot 项目)

如果你使用的是 Spring Boot,推荐写法如下:

@Service
public class ActorService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void updateActorsInBatches(List<Actor> actors) {
        jdbcTemplate.batchUpdate(
            "UPDATE t_actor SET first_name = ?, last_name = ? WHERE id = ?",
            actors,
            100,
            (ps, actor) -> {
                ps.setString(1, actor.getFirstName());
                ps.setString(2, actor.getLastName());
                ps.setLong(3, actor.getId());
            }
        );
    }
}

搭配事务注解:

@Transactional
public void importHugeData() {
    List<Actor> hugeList = readFromCsvOrDatabase();
    updateActorsInBatches(hugeList); // 分批提交
}

总结一句话:

批量操作 = 把多条 SQL “打包”发送给数据库,减少网络来回,提高性能。Spring 提供了多种方式让你既能简单使用,也能精细控制。

下一步你可以思考:

  • 如何从 CSV 文件逐行读取并分批导入?
  • 如何在批量操作中捕获部分失败的记录?
  • 如何结合 @Async 实现并行批量处理?

以上就是Spring Framework中JDBC批量操作的三种实现方式的详细内容,更多关于Spring JDBC批量操作方式的资料请关注脚本之家其它相关文章!

相关文章

  • java 学习笔记(入门篇)_java程序helloWorld

    java 学习笔记(入门篇)_java程序helloWorld

    安装配置完Java的jdk,下面就开始写第一个java程序--hello World.用来在控制台输出“Hello World”,接下来详细介绍,感兴趣的朋友可以参考下
    2013-01-01
  • 用Java实现连连看小游戏

    用Java实现连连看小游戏

    这篇文章主要为大家详细介绍了用Java实现连连看小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java实现聊天室界面

    Java实现聊天室界面

    这篇文章主要为大家详细介绍了Java实现聊天室界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java实现的并发任务处理实例

    Java实现的并发任务处理实例

    这篇文章主要介绍了Java实现的并发任务处理方法,结合实例形式较为详细的分析了基于线程操作并发任务的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-11-11
  • SpringBoot定时任务设计之时间轮案例原理详解

    SpringBoot定时任务设计之时间轮案例原理详解

    这篇文章主要为大家介绍了SpringBoot定时任务设计之时间轮案例原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • 使用idea搭建spring项目,利用xml文件的形式进行配置方式

    使用idea搭建spring项目,利用xml文件的形式进行配置方式

    本文介绍了如何使用SpringIOC和SpringDI的思想开发一个打印机模拟程序,实现了灵活配置彩色墨盒或灰色墨盒以及打印页面大小的功能,通过创建接口和实现类,并在配置文件中进行依赖注入,实现了控制反转
    2024-11-11
  • springdata jpa使用Example快速实现动态查询功能

    springdata jpa使用Example快速实现动态查询功能

    这篇文章主要介绍了springdata jpa使用Example快速实现动态查询功能,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Maven  pom.xml与settings.xml详解

    Maven pom.xml与settings.xml详解

    这篇文章主要介绍了Maven pom.xml与settings.xml详解的相关资料,这里对pom.xml与setting.xml详细的研究说明,需要的朋友可以参考下
    2016-12-12
  • 深入浅析TomCat Session管理分析

    深入浅析TomCat Session管理分析

    这篇文章主要介绍了深入浅析TomCat Session管理分析,需要的朋友可以参考下
    2015-11-11
  • Java的lambda表达式实现解析

    Java的lambda表达式实现解析

    这篇文章主要为大家详细介绍了Java的lamda表达式实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06

最新评论