bigywater.tech
Spring · 95

JdbcTemplate 与 NamedParameterJdbcTemplate:RowMapper 与批量

JdbcTemplate 查不到行会抛什么异常?BeanPropertyRowMapper 列名对不上会不会报错?IN 列表是空的怎么办?batchUpdate 真的把 SQL 合并了吗?update 返回的行数是什么意思?这篇在 Spring Framework 7、HikariCP、MySQL 上逐个实测。

5000 条
batchUpdate 在默认 MySQL 参数下,服务端仍然执行 5000 条 INSERT
in ()
NamedParameterJdbcTemplate 传空集合,SQL 变成 in (),抛 BadSqlGrammarException
1 和 1
值真正改变的 update 与值没变的 update,返回的都是 1

查询与映射

实测 · 本机执行输出
queryForObject("select count(*) from person", Integer.class)  ⇒ 3
queryForObject 没有匹配的行  ⇒ 抛 EmptyResultDataAccessException:Incorrect result size: expected 1, actual 0
queryForObject 有 2 行匹配  ⇒ 抛 IncorrectResultSizeDataAccessException:Incorrect result size: expected 1, actual 2
queryForObject 返回 null 列(email 为 NULL)  ⇒ null
query(sql, RowMapper) 手写 lambda  ⇒ 2 行 [Ann#0, Bob#1]
BeanPropertyRowMapper<Person>(下划线列名 → 驼峰属性)  ⇒ 3 行 [Person[1,Ann Lee,30,ann@x], Person[2,Bob Lee,25,null], Person[3,Cid Ray,41,cid@x]]
DataClassRowMapper<PersonRec>(record)  ⇒ 3 行 [PersonRec[id=1, firstName=Ann, lastName=Lee, age=30, email=ann@x], PersonRec[id=2, firstName=Bob, lastName=Lee, age=25, email=null], PersonRec[id=3, firstName=Cid, lastName=Ray, age=41, email=cid@x]]
BeanPropertyRowMapper,但 select 里把 first_name 改名成 fname  ⇒ 1 行 [Person[1,null Lee,30,ann@x]]
JdbcClient:sql(...).param(...).query(Person.class).list()  ⇒ 2 行 [Person[1,Ann Lee,30,ann@x], Person[2,Bob Lee,25,null]]
JdbcClient:单值 .query(String.class).single() 没有匹配的行  ⇒ 抛 EmptyResultDataAccessException:Incorrect result size: expected 1, actual 0
JdbcClient:.optional()  ⇒ Optional.empty

queryForObject 0 行与多行分别抛不同异常;BeanPropertyRowMapper 下划线转驼峰,对不上的列静默忽略;JdbcClient 有 single 和 optional。

命名参数

实测 · 本机执行输出
命名参数:where last_name = :ln and age > :age  ⇒ 1 行 [Ann]
IN 列表展开:id in (:ids),传入 List.of(1, 3)  ⇒ 2 行 [Ann, Cid]
IN 列表是空集合  ⇒ 抛 BadSqlGrammarException:PreparedStatementCallback; bad SQL grammar [select first_name from person where id in ()]
同一个命名参数用两次  ⇒ 1 行 [Cid]
少传了一个命名参数  ⇒ 抛 InvalidDataAccessApiUsageException:No value supplied for the SQL parameter 'age': No value registered for key 'age'
BeanPropertySqlParameterSource(从对象取参数)  ⇒ 1 行 [Cid]

IN 列表自动展开,空集合变成语法错误;漏传参数明确报错。

批量

实测 · 本机执行输出
  循环里一行一个 update:中位 614 ms;服务端执行的 INSERT 语句数 Com_insert +5000;表里 5000 行
  batchUpdate(sql, List<Object[]>):中位 517 ms;服务端执行的 INSERT 语句数 Com_insert +5000;表里 5000 行
  NamedParameterJdbcTemplate.batchUpdate(sql, SqlParameterSource[]):中位 499 ms;服务端执行的 INSERT 语句数 Com_insert +5000;表里 5000 行

默认 MySQL 参数下 batchUpdate 只省往返,不合并语句。

更新与主键

实测 · 本机执行输出
第一次 update person set email = 'new@x' where id = 1(值确实变了)  ⇒ 1
第二次执行同一条(值没有变)  ⇒ 1
update 不存在的 id  ⇒ 0
GeneratedKeyHolder 取自增主键  ⇒ 新行 id = 4
省了 RETURN_GENERATED_KEYS  ⇒ 抛 TransientDataAccessResourceException:PreparedStatementCallback; Generated keys not requested. You need to specify Statement.RETURN_GENERATED_KEYS to Statement.executeUpdate(), Statement.executeLargeUpdate() or Connection.prepareStatement(…
上一条抛了异常,但那行 Eve 是不是已经插进去了(auto-commit)  ⇒ person 里 first_name='Eve' 的行数 = 1
JdbcClient:.update(keyHolder)  ⇒ 影响 1 行,新行 id = 6

update 返回匹配的行数;取自增主键要 RETURN_GENERATED_KEYS,否则 INSERT 先执行后抛异常。

分步演示

JdbcTemplate
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Framework 7.0.9 的 JdbcTemplate、NamedParameterJdbcTemplate、JdbcClient;HikariCP;MySQL 是本机自己起的 26.7.0 实例,mysql-connector-j 默认连接参数,没有开 rewriteBatchedStatements 或 useAffectedRows)。person 表 3 行;批量 5000 行 insert,每种写法 3 次取中位数,服务端语句数取自 SHOW GLOBAL STATUS 的 Com_insert 差值
    • 没有验证:rewriteBatchedStatements=true 之后的批量(见第 96 篇);useAffectedRows=true 之后 update 的返回值;SimpleJdbcInsert / SimpleJdbcCall;存储过程;ResultSetExtractor 与流式读取;JdbcTemplate 的 fetchSize 与 queryTimeout;事务内多次调用共用连接(见事务篇)
    京ICP备2026067502号-1京公网安备11010502063487号