⚡ Spring JDBC 完整体系 · 第 7 讲 · NamedParameterJdbcTemplate 命名参数模板

⚡ Spring JDBC 完整体系 · 第 7 讲 · NamedParameterJdbcTemplate 命名参数模板

相关开源地址

  1. 核心框架源码:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例(本集源码):https://gitee.com/gao_zhenzhong/simple-dao-demo

📋 前置知识

✅ 你只需要会❌ 你完全不需要会的
Java 基础 + Spring Boot 基本用法Spring 自动配置源码细节
第 6 讲:JdbcTemplate 的 ? 占位符传参MyBatis / Hibernate 任何知识
基础 SQL(INSERT、UPDATE、DELETE、SELECT)连接池调优细节

📚 全套教程总览(20 讲从零到架构师)

篇章涵盖讲次核心目标
开篇筑基第 1-3 讲认知梳理,建立 Spring JDBC 的底层坐标系
API 精讲第 4-7 讲全面掌握 JdbcTemplate + 事务体系
工具封装第 8-9 讲手搓条件工具 + 代码生成器,从“使用者”变“创造者”
认知升华第 10 讲模板确定性 vs AI 不确定性,建立技术判断力
高阶扩展第 11-17 讲多数据源、分库分表、分布式事务
底层观测第 18-19 讲SQL 拦截、性能监控、连接池调优
终极收尾第 20 讲全链路复盘 + 手搓持久层框架

一、开篇 · 上一讲的痛点:记不住 ? 的顺序

第 6 讲我们用 JdbcTemplate? 占位符完成了增删改查。但有一个问题随着参数增多会越来越明显——顺序记忆

// 参数少的时候还好
jdbcTemplate.update(
    "INSERT INTO sys_user (user_name, birthday, gender) VALUES(?, ?, ?)",
    userName, birthday, gender
);

// 参数一多(5个、8个、10个),顺序记忆就非常痛苦
jdbcTemplate.update(
    "UPDATE sys_user SET user_name=?, birthday=?, gender=?, phone=?, email=?, address=? WHERE user_id=?",
    userName, birthday, gender, phone, email, address, userId
);

问题本质? 占位符只依赖位置,不携带任何语义信息。开发者必须人工数出 SQL 中 ? 的顺序,再按完全相同的顺序排列参数。数错一位,轻则数据错乱,重则类型转换异常。

这一讲引入 NamedParameterJdbcTemplate——用 :name 命名参数替代 ?,彻底告别顺序记忆。

二、依赖与配置

2.1 pom.xml · 无需新增依赖

NamedParameterJdbcTemplateJdbcTemplate 同属 spring-jdbc 模块:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

已在第 5 讲引入,无需额外添加任何依赖

2.2 application.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:
  sql:
    init:
      schema-locations: classpath:schema.sql
      mode: always

2.3 schema.sql

DROP TABLE IF EXISTS sys_user;
CREATE TABLE sys_user (
    user_id   BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_name VARCHAR(50),
    birthday  DATE,
    gender    TINYINT
);

2.4 User.java · 实体类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long userId;
    private String userName;
    private LocalDate birthday;
    private Byte gender;
}

三、NamedParameterJdbcTemplate 的自动配置

Spring Boot 已预置 NamedParameterJdbcTemplate 的自动配置,直接在 DAO 中注入即可使用,无需手动声明任何 Bean:

@Repository
public class UserDao {

    @Autowired
    private NamedParameterJdbcTemplate namedJdbc;  // 直接注入,开箱即用
}

自动配置原理与第 5 讲的 JdbcTemplateAutoConfiguration 一致——Spring Boot 在 org.springframework.boot.autoconfigure.jdbc 包中预置了 JdbcTemplateAutoConfiguration,它会同时创建 JdbcTemplateNamedParameterJdbcTemplate 两个 Bean,前提是 classpath 中存在对应的类且开发者没有手动覆盖。

四、案例1 · INSERT 命名参数

public void demo01() {
    String sql = "INSERT INTO sys_user (user_name, birthday, gender) " +
                 "VALUES (:userName, :birthday, :gender)";

    MapSqlParameterSource params = new MapSqlParameterSource()
        .addValue("userName", "张三")
        .addValue("birthday", LocalDate.now())
        .addValue("gender", (byte) 1);

    namedJdbc.update(sql, params);
}

核心变化

对比项JdbcTemplateNamedParameterJdbcTemplate
SQL 写法VALUES(?, ?, ?)VALUES(:userName, :birthday, :gender)
参数绑定? 顺序传入通过 MapSqlParameterSource 按名称绑定
顺序要求必须严格对应无关顺序

MapSqlParameterSource 是 Spring 提供的 SqlParameterSource 接口实现类,底层基于 LinkedHashMap 存储键值对。它支持链式调用 .addValue(key, value),每调用一次向内部 Map 存入一个键值对,最终在 NamedParameterJdbcTemplate 执行时被解析为 PreparedStatement? 参数数组。参数顺序由 NamedParameterJdbcTemplate 在解析 SQL 时根据 :name 的出现顺序自动决定。

参数绑定顺序不再重要,这是 NamedParameterJdbcTemplateJdbcTemplate 最本质的区别。

执行日志

[DEBUG] Executing prepared SQL update [INSERT INTO sys_user (user_name, birthday, gender) VALUES (?, ?, ?)]
[TRACE] column index 1, parameter value [张三], value class [java.lang.String]
[TRACE] column index 2, parameter value [2026-08-03], value class [java.time.LocalDate]
[TRACE] column index 3, parameter value [1], value class [java.lang.Byte]

注意:底层最终执行的仍然是 PreparedStatement? 占位符。NamedParameterJdbcTemplate 做了两件事:

  1. 将 SQL 中的 :name 替换为 ?
  2. 根据解析出的名称顺序,从 MapSqlParameterSource 中提取对应的值,组装成参数数组

所以命名参数本质上是 编译期的语法糖,运行期仍然是标准的 ? 占位符。

五、案例2 · 自增主键回填

插入数据后,经常需要获取数据库自动生成的主键 ID。NamedParameterJdbcTemplate 配合 KeyHolder 实现主键回填:

public void demo02() {
    String sql = "INSERT INTO sys_user (user_name, birthday, gender) " +
                 "VALUES (:userName, :birthday, :gender)";

    MapSqlParameterSource params = new MapSqlParameterSource()
        .addValue("userName", "李四")
        .addValue("birthday", LocalDate.now())
        .addValue("gender", (byte) 0);

    KeyHolder keyHolder = new GeneratedKeyHolder();
    namedJdbc.update(sql, params, keyHolder, new String[]{"user_id"});

    Long generatedId = keyHolder.getKey().longValue();
    log.info("生成的ID:{}", generatedId);
}

关键点

要素说明
KeyHolderSpring 提供的自增主键持有器接口
GeneratedKeyHolderKeyHolder 的标准实现
new String[]{"user_id"}指定要返回的列名(自增主键字段)
keyHolder.getKey()获取生成的第一个主键值,返回 Number 类型

KeyHolder 的底层机制依赖于 JDBC 驱动的 Statement.getGeneratedKeys() 方法。NamedParameterJdbcTemplate 在执行 update(sql, params, keyHolder, keyColumnNames) 时,会调用 PreparedStatementprepareStatement(sql, keyColumnNames) 重载,该重载会向数据库发送“返回指定列”的指令,执行完成后通过 getGeneratedKeys() 获取 ResultSet,并将结果填充到 KeyHolder 中。如果自增主键是 BIGINT 类型,getKey().longValue() 可安全获取数值。

执行日志

[INFO] 生成的ID:1

六、案例3 · 批量插入

插入多条记录时,逐条发送 SQL 会产生大量网络往返(Round-Trip)。NamedParameterJdbcTemplatebatchUpdate 支持一次发送多条记录:

public void demo03() {
    String sql = "INSERT INTO sys_user (user_name, birthday, gender) " +
                 "VALUES (:userName, :birthday, :gender)";

    SqlParameterSource[] batchParams = new SqlParameterSource[] {
        new MapSqlParameterSource()
            .addValue("userName", "王五")
            .addValue("birthday", LocalDate.now())
            .addValue("gender", (byte) 1),
        new MapSqlParameterSource()
            .addValue("userName", "赵六")
            .addValue("birthday", LocalDate.now().plusDays(1))
            .addValue("gender", (byte) 0)
    };

    namedJdbc.batchUpdate(sql, batchParams);
}

核心要点

要素说明
SqlParameterSource[]参数源数组,每个元素对应一条记录
MapSqlParameterSourceSqlParameterSource 的实现类,基于 Map 存储
一次发送多条记录打包成一条批量 SQL 发送,减少网络往返

batchUpdate 的执行机制是:将 SqlParameterSource[] 中每条记录的参数依次绑定到同一个 SQL 模板上,然后以 JDBC 的 PreparedStatement.addBatch() + executeBatch() 方式一次性提交给数据库。相比单条逐次执行,批量操作在网络 I/O 次数上减少了 N-1 次(N 为记录数),显著提升了写入吞吐量。

执行日志

[DEBUG] Executing batch update for 2 statements

七、案例4 · 查询 · 命名参数同样适用

NamedParameterJdbcTemplate 不仅支持增删改,查询同样支持命名参数:

public void demo04() {
    String sql = "SELECT user_id, user_name, birthday, gender " +
                 "FROM sys_user WHERE user_id > :minId AND gender = :gender";

    MapSqlParameterSource params = new MapSqlParameterSource()
        .addValue("minId", 0L)
        .addValue("gender", (byte) 1);

    List<User> users = namedJdbc.query(
        sql,
        params,
        new BeanPropertyRowMapper<>(User.class)
    );
    log.info("users={}", users);
}

八、NamedParameterJdbcTemplate vs JdbcTemplate

维度JdbcTemplateNamedParameterJdbcTemplate
参数占位符?:name
参数绑定顺序必须严格对应无关顺序
SQL 可读性参数多时较差清晰,自带语义
参数绑定方式直接传入或 Object[]MapSqlParameterSource / Map / SqlParameterSource[]
主键回填支持 KeyHolder支持 KeyHolder
批量操作batchUpdate(sql, List<Object[]>)batchUpdate(sql, SqlParameterSource[])
适用场景参数少(≤3个)参数多(≥3个)或需语义化参数名
依赖spring-jdbcspring-jdbc(同模块)

选型建议

JdbcTemplate? 占位符适用于参数较少(通常 ≤ 3 个)的场景,此时顺序记忆成本低,代码更简洁。一旦参数数量增多,或 SQL 结构复杂(如动态条件拼接),NamedParameterJdbcTemplate 的语义化参数名能显著提升代码可读性和可维护性。两者可以混用——同一个 DAO 中同时注入两个模板,根据场景灵活选择。

九、三种参数传递方式的演进路线

从第 1 讲的裸 JDBC 到本讲的命名参数,参数传递方式经历了清晰的演进:

讲次方式示例特点适用场景
第 1 讲SQL 硬编码VALUES('张三', 1)值直接写在 SQL 中,参数无法复用数据初始化、种子数据
第 6 讲? 占位符VALUES(?, ?)SQL 与参数分离,顺序必须对应参数少(≤3个)的通用场景
第 7 讲:name 命名参数VALUES(:name, :gender)参数名称化,顺序无关参数多或需语义化参数名的场景

三种方式各有其适用边界,并非绝对替代关系。开发者在实际编码中应根据参数数量、SQL 复杂度、可维护性需求做出技术判断。

十、本讲核心总结

核心要点说明
无需新增依赖NamedParameterJdbcTemplate 同属 spring-jdbc 模块
Spring Boot 自动装配直接 @Autowired 注入即可,无需手动配置
命名参数语法SQL 中用 :name 替代 ?,用 MapSqlParameterSource 绑定值
参数顺序无关命名参数的核心价值——彻底告别数 ? 的烦恼
主键回填KeyHolder + namedJdbc.update(sql, params, keyHolder, keyColNames)
批量操作SqlParameterSource[] + namedJdbc.batchUpdate(sql, batchParams)
查询同样支持namedJdbc.query(sql, params, rowMapper)

本质理解

NamedParameterJdbcTemplateJdbcTemplate语义化包装。它不替代 JdbcTemplate,而是在其基础上增加了命名参数的解析能力。底层执行时,命名参数会被解析为 ?,最终仍然由 PreparedStatement 完成预编译和参数绑定。两者是增强关系,而非替代关系。

十一、下集预告

第 8 讲将进入 查询结果四种形态

单行单列 → queryForObject
单行多列 → queryForObject + RowMapper
多行单列 → queryForList
多行多列 → query + RowMapper
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值