查询构建器(JdbcTemplate)
Laravel 的
DB::table('posts')->where(...)->get()返回「对象集合」;Spring 的 JdbcTemplate 手写 SQL、结果手动映射,是「不想要 ORM 时」的轻量选择,也是理解 JDBC 的最佳入口。
JdbcTemplate 是什么
Spring 对原生 JDBC 的封装:帮你管连接、预编译、结果集转换,你只管写 SQL 和接收结果。它不生成 SQL(不像 ORM),但省掉了大量 JDBC 样板代码。
注入使用
@Repository
public class PostDao {
private final JdbcTemplate jdbc;
public PostDao(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
}Spring Boot 自动配置了一个 JdbcTemplate Bean(前提是配了 DataSource),直接注入。
查询:queryForList / queryForObject
查多行
// 返回 List<Map>,每行一个 Map(列名 → 值)
List<Map<String, Object>> rows = jdbc.queryForList("SELECT id, title FROM posts WHERE user_id = ?", userId);
// 用法
for (Map<String, Object> row : rows) {
System.out.println(row.get("id") + " - " + row.get("title"));
}对应 Laravel DB::table('posts')->get()->toArray()。
查单行单列
// 单值
Integer count = jdbc.queryForObject("SELECT COUNT(*) FROM posts", Integer.class);
// 单行(返回 Map)
Map<String, Object> post = jdbc.queryForMap("SELECT * FROM posts WHERE id = ?", id);WARNING
queryForObject 查不到结果会抛 EmptyResultDataAccessException,需要捕获或用「返回 Optional 的重载」:
Optional<Map<String, Object>> post = jdbc.query(
"SELECT * FROM posts WHERE id = ?",
rs -> rs.next() ? Map.of(
"id", rs.getLong("id"),
"title", rs.getString("title")) : null, id);参数绑定:一律用 ?
jdbc.queryForList("SELECT * FROM posts WHERE title LIKE ? AND status = ?",
"%" + keyword + "%", 1);TIP
用 ? 占位 + 参数传入,杜绝字符串拼接 SQL,和 Laravel 里用绑定参数防注入是同一个道理。JDBC 层就是靠这个防 SQL 注入的。
写操作:update
int affected = jdbc.update("INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)",
title, content, userId);
int affected = jdbc.update("UPDATE posts SET title = ? WHERE id = ?", title, id);
int affected = jdbc.update("DELETE FROM posts WHERE id = ?", id);对应 DB::insert / update / delete,返回受影响行数。
拿自增主键
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbc.update(con -> {
PreparedStatement ps = con.prepareStatement(
"INSERT INTO posts (title, content) VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, title);
ps.setString(2, content);
return ps;
}, keyHolder);
Number key = keyHolder.getKey(); // 新插入的 id结果映射到实体:RowMapper
每次查出来是 Map,用起来手写字段麻烦。定义 RowMapper 把行映射成实体:
public class PostRowMapper implements RowMapper<Post> {
@Override
public Post mapRow(ResultSet rs, int rowNum) throws SQLException {
Post post = new Post();
post.setId(rs.getLong("id"));
post.setTitle(rs.getString("title"));
post.setContent(rs.getString("content"));
post.setUserId(rs.getLong("user_id"));
return post;
}
}List<Post> posts = jdbc.query("SELECT * FROM posts WHERE status = ?", new PostRowMapper(), 1);
Post post = jdbc.queryForObject("SELECT * FROM posts WHERE id = ?", new PostRowMapper(), id);TIP
RowMapper ≈ Laravel 里把查询结果手动转成 Model 的 Model::hydrate()。手写一行行 set 是 JdbcTemplate 的痛点,也是为什么大多数人选 JPA/MyBatis 的原因 —— 但它们底层都离不开这种映射,理解它有助于理解 ORM 原理。
批量操作:batchUpdate
jdbc.batchUpdate("INSERT INTO posts (title) VALUES (?)",
listOfTitles.stream().map(t -> new Object[]{t}).toList());动态 SQL(简单版)
Laravel 有 when() 条件拼接;JdbcTemplate 没内置,手写拼接时注意参数占位:
public List<Post> search(String keyword, Integer status) {
StringBuilder sql = new StringBuilder("SELECT * FROM posts WHERE 1=1");
List<Object> params = new ArrayList<>();
if (keyword != null && !keyword.isBlank()) {
sql.append(" AND title LIKE ?");
params.add("%" + keyword + "%");
}
if (status != null) {
sql.append(" AND status = ?");
params.add(status);
}
return jdbc.query(sql.toString(), new PostRowMapper(), params.toArray());
}TIP
WHERE 1=1 是 Java 动态 SQL 的经典写法,后面再 AND 拼接永远语法正确。复杂动态 SQL 建议用 MyBatis(它有专门的 XML 动态 SQL 标签),JdbcTemplate 手拼只适合简单场景。
事务
@Transactional
public void createWithDependencies(PostDto dto) {
jdbc.update("INSERT INTO posts (title) VALUES (?)", dto.getTitle());
jdbc.update("INSERT INTO logs (message) VALUES (?)", "创建帖子");
}和 Eloquent 一样,事务用 @Transactional 统一管理(见 数据库连接配置)。
JdbcTemplate 对照速查
| 需求 | Laravel Query Builder | JdbcTemplate |
|---|---|---|
| 全表查 | DB::table('posts')->get() | jdbc.queryForList("SELECT * FROM posts") |
| where | ->where('status', 1) | SQL 里写 WHERE status = ? |
| 单条 | ->find($id) | queryForObject |
| 插入 | ->insert([...]) | jdbc.update("INSERT ...") |
| 更新 | ->update([...]) | jdbc.update("UPDATE ...") |
| 删除 | ->delete() | jdbc.update("DELETE ...") |
| 聚合 | ->count() | queryForObject("SELECT COUNT(*)...") |
| 批量 | ->insert([...]) 多条 | jdbc.batchUpdate(...) |
| 事务 | DB::transaction(...) | @Transactional |
一句话
JdbcTemplate = 「SQL 全手写 + 参数绑定防注入 + 手动映射结果」。想完全掌控 SQL、又不想上 MyBatis 时的选择。下一节:分页。