Spring JDBC高级操作全解析
这段内容是关于 Spring Framework 中 JDBC 操作的高级用法,主要分为两大部分:
SimpleJdbc类:简化 JDBC 增删改查操作。- 将 JDBC 操作建模为 Java 对象:通过面向对象的方式封装数据库操作。
第一部分:使用 SimpleJdbc 简化 JDBC 操作
核心思想
- 使用
SimpleJdbcInsert和SimpleJdbcCall这两个类来减少配置代码。 - 利用 JDBC 驱动提供的元数据(metadata)自动推断表结构、列名、参数类型等信息,从而避免手动声明。
- 支持“流式 API”风格(fluid style),允许链式调用配置方法。
3.6.1 插入数据 (SimpleJdbcInsert)
核心步骤:
- 在 DAO 初始化时创建
SimpleJdbcInsert实例,并指定数据源和表名。 - 将要插入的数据放入一个
Map<String, Object>,其中 key 是数据库字段名,value 是对应的值。 - 调用
.execute(parameters)执行插入。
✅ 优点:
- 不需要写 SQL 语句。
- 自动利用元数据生成 INSERT 语句。
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");Map<String, Object> parameters = new HashMap<>();
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());insertActor.execute(parameters);
3.6.2 获取自动生成的主键
如果主键是数据库自增的(如 MySQL 的 AUTO_INCREMENT),可以使用 .usingGeneratedKeyColumns("id") 来告诉 Spring 返回这个生成的 ID。
然后调用 .executeAndReturnKey() 方法获取主键值。
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingGeneratedKeyColumns("id");// 注意这里不传 id
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue()); // 设置回实体
📌 返回的是 java.lang.Number 类型,因为不同数据库返回的具体类型可能不同(Long、Integer 等)。
3.6.3 显式指定插入列
如果不希望插入所有字段(比如有些字段有默认值或不允许为空但不想设值),可以用 .usingColumns(...) 明确指定要插入哪些列。
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor").usingColumns("first_name", "last_name") // 只插这两个字段.usingGeneratedKeyColumns("id");
这样即使 Map 中有多余字段也不会报错,只会处理指定的列。
3.6.4 使用 SqlParameterSource 提供参数值
除了使用 Map,还可以使用更方便的 SqlParameterSource 接口实现类:
✅ BeanPropertySqlParameterSource
如果你有一个符合 JavaBean 规范的对象(getter/setter),可以直接传入该对象。它会自动通过 getter 提取属性值作为参数。
SqlParameterSource params = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(params);
前提是对象的属性名与数据库字段名一致(或通过命名策略映射)。
✅ MapSqlParameterSource
比普通 HashMap 更友好的 map 实现,支持链式添加参数。
SqlParameterSource params = new MapSqlParameterSource().addValue("first_name", actor.getFirstName()).addValue("last_name", actor.getLastName());
3.6.5 调用存储过程 (SimpleJdbcCall)
用于调用数据库中的 存储过程(Stored Procedure)。
假设有个 MySQL 存储过程:
CREATE PROCEDURE read_actor (IN in_id INTEGER,OUT out_first_name VARCHAR(100),OUT out_last_name VARCHAR(100),OUT out_birth_date DATE)
BEGINSELECT first_name, last_name, birth_dateINTO out_first_name, out_last_name, out_birth_dateFROM t_actor WHERE id = in_id;
END;
在 Java 中调用:
this.procReadActor = new SimpleJdbcCall(dataSource).withProcedureName("read_actor"); // 指定过程名// 输入参数
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);// 执行并获得输出结果(Map)
Map<String, Object> out = procReadActor.execute(in);// 构造 Actor 对象
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String)out.get("out_first_name"));
actor.setLastName((String)out.get("out_last_name"));
actor.setBirthDate((Date)out.get("out_birth_date"));
🔍 注意:
- 参数名大小写不敏感(Spring 使用元数据匹配)。
- 输出参数也以 name-value 形式存在返回的 Map 中。
💡 建议开启忽略大小写的结果映射:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true); // 忽略大小写
this.procReadActor = new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_actor");
这能防止因数据库返回字段名为大写/小写导致取不到值的问题。
3.6.6 显式声明参数(适用于不支持元数据读取的数据库)
某些数据库(如 PostgreSQL)可能不被 Spring 完全支持元数据读取,此时需要手动声明参数。
你可以使用 .declareParameters(...) 明确指定每个参数的名称和类型。
this.procReadActor = new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_actor").withoutProcedureColumnMetaDataAccess() // 关闭元数据访问.useInParameterNames("in_id") // 指定输入参数名.declareParameters(new SqlParameter("in_id", Types.NUMERIC),new SqlOutParameter("out_first_name", Types.VARCHAR),new SqlOutParameter("out_last_name", Types.VARCHAR),new SqlOutParameter("out_birth_date", Types.DATE));
📌 Types 来自 java.sql.Types,表示 JDBC 数据类型常量。
3.6.7 如何定义 SqlParameters
| 类型 | 说明 |
|---|---|
SqlParameter | 输入参数(IN) |
SqlOutParameter | 输出参数(OUT) |
SqlInOutParameter | 既输入又输出(INOUT) |
这些参数用于 SimpleJdbcCall 或后续提到的 StoredProcedure。
例如:
new SqlParameter("in_id", Types.INTEGER)
new SqlOutParameter("result", Types.VARCHAR)
还可以附加额外信息,如精度、scale、自定义类型名、RowMapper(用于 REF CURSOR)等。
3.6.8 调用存储函数 (SimpleJdbcCall with Function)
类似调用存储过程,但用于调用 函数(Function),通常有返回值。
MySQL 函数示例:
CREATE FUNCTION get_actor_name (in_id INTEGER) RETURNS VARCHAR(200)
READS SQL DATA
BEGINDECLARE out_name VARCHAR(200);SELECT concat(first_name, ' ', last_name) INTO out_name FROM t_actor WHERE id = in_id;RETURN out_name;
END;
Java 调用方式:
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate).withFunctionName("get_actor_name"); // 注意这里是 functionpublic String getActorName(Long id) {SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);return funcGetActorName.executeFunction(String.class, in); // 直接返回 String
}
✅ .executeFunction(returnType, inputParams) 直接返回函数结果,无需从 Map 取。
3.6.9 从存储过程返回 ResultSet 或 REF Cursor
当存储过程返回结果集时(比如查询多条记录),需要用 .returningResultSet() 注册一个 RowMapper 来处理每行数据。
MySQL 示例:
CREATE PROCEDURE read_all_actors()
BEGINSELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
Java 处理:
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate).withProcedureName("read_all_actors").returningResultSet("actors", BeanPropertyRowMapper.newInstance(Actor.class)); // 映射到 Actor 列表public List<Actor> getActorsList() {Map<String, Object> result = procReadAllActors.execute(Collections.emptyMap());return (List<Actor>) result.get("actors"); // 获取映射后的列表
}
✅ BeanPropertyRowMapper 可自动将列映射到同名属性上(驼峰转下划线等规则也可配置)。
第二部分:将 JDBC 操作建模为 Java 对象(RDBMS Operation Classes)
Spring 提供了一套面向对象的方式来封装数据库操作。虽然现在很多人更倾向于直接使用 JdbcTemplate,但在复杂场景下仍有价值。
3.7.1 & 3.7.2 SqlQuery / MappingSqlQuery
封装可重用的查询逻辑,特别是将结果映射为 Java 对象。
示例:根据 ID 查询演员
public class ActorMappingQuery extends MappingSqlQuery<Actor> {public ActorMappingQuery(DataSource ds) {super(ds, "SELECT id, first_name, last_name FROM t_actor WHERE id = ?");declareParameter(new SqlParameter("id", Types.INTEGER));compile(); // 编译准备语句}@Overrideprotected Actor mapRow(ResultSet rs, int rowNum) throws SQLException {Actor actor = new Actor();actor.setId(rs.getLong("id"));actor.setFirstName(rs.getString("first_name"));actor.setLastName(rs.getString("last_name"));return actor;}
}
使用:
Actor actor = actorMappingQuery.findObject(id); // findObject 返回单个对象
List<Actor> actors = actorSearchMappingQuery.execute(age, pattern); // execute 返回列表
✅ 特点:
- 线程安全(编译后不可变)
- 可复用
- 强类型查询封装
3.7.3 SqlUpdate
封装更新操作(INSERT/UPDATE/DELETE)。
示例:更新客户信用评级
public class UpdateCreditRating extends SqlUpdate {public UpdateCreditRating(DataSource ds) {setDataSource(ds);setSql("UPDATE customer SET credit_rating = ? WHERE id = ?");declareParameter(new SqlParameter("creditRating", Types.NUMERIC));declareParameter(new SqlParameter("id", Types.NUMERIC));compile();}public int execute(int id, int rating) {return update(rating, id); // 调用父类 update 方法}
}
返回受影响行数。
3.7.4 StoredProcedure
抽象类,用于封装对数据库存储过程的调用。
特点:
- 必须继承它
- 支持输入、输出、输入输出参数
- 更灵活,适合复杂过程调用
示例:调用 Oracle 的 sysdate 函数
private class GetSysdateProcedure extends StoredProcedure {public GetSysdateProcedure(DataSource dataSource) {setDataSource(dataSource);setFunction(true); // 表示这是一个函数setSql("sysdate");declareParameter(new SqlOutParameter("date", Types.DATE));compile();}public Date execute() {Map<String, Object> results = execute(new HashMap<>());return (Date) results.get("date");}
}
另一个例子:返回多个 REF CURSOR(Oracle 特有)
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
配合 RowMapper 使用,可以把游标结果映射成对象列表。
总结对比
| 功能 | 推荐方式 | 说明 |
|---|---|---|
| 简单插入 | SimpleJdbcInsert | 最简洁,自动利用元数据 |
| 调用存储过程 | SimpleJdbcCall | 推荐首选,配置简单 |
| 调用函数 | SimpleJdbcCall.withFunctionName() | 直接返回函数值 |
| 复杂映射查询 | MappingSqlQuery | 封装固定查询逻辑 |
| 更新操作 | SqlUpdate 或直接 JdbcTemplate.update() | 后者更常用 |
| 复杂存储过程(多结果集、REF CURSOR) | StoredProcedure | 更强大,适合 Oracle 等企业级数据库 |
💡 实际开发建议
- 优先使用
JdbcTemplate+ Lambda(现代 Spring Boot 风格) - 如果想进一步简化 CRUD,考虑使用 Spring Data JPA / MyBatis / QueryDSL
SimpleJdbcInsert和SimpleJdbcCall适合快速集成已有数据库过程或简单批量操作。MappingSqlQuery和SqlUpdate属于较老的设计模式,现在较少单独使用。StoredProcedure主要在需要对接遗留系统或 Oracle PL/SQL 时使用。
如有具体问题(比如某个类怎么用、报错分析、性能优化),欢迎继续提问!
