文档站点
Skip to content

查询构建器(JdbcTemplate) ​

Laravel 的 DB::table('posts')->where(...)->get() 返回「对象集合」;Spring 的 JdbcTemplate 手写 SQL、结果手动映射,是「不想要 ORM 时」的轻量选择,也是理解 JDBC 的最佳入口。

JdbcTemplate 是什么 ​

Spring 对原生 JDBC 的封装:帮你管连接、预编译、结果集转换,你只管写 SQL 和接收结果。它不生成 SQL(不像 ORM),但省掉了大量 JDBC 样板代码。

注入使用 ​

java
@Repository
public class PostDao {
    private final JdbcTemplate jdbc;

    public PostDao(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }
}

Spring Boot 自动配置了一个 JdbcTemplate Bean(前提是配了 DataSource),直接注入。

查询:queryForList / queryForObject ​

查多行 ​

java
// 返回 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()。

查单行单列 ​

java
// 单值
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 的重载」:

java
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);

参数绑定:一律用 ? ​

java
jdbc.queryForList("SELECT * FROM posts WHERE title LIKE ? AND status = ?",
        "%" + keyword + "%", 1);

TIP

用 ? 占位 + 参数传入,杜绝字符串拼接 SQL,和 Laravel 里用绑定参数防注入是同一个道理。JDBC 层就是靠这个防 SQL 注入的。

写操作:update ​

java
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,返回受影响行数。

拿自增主键 ​

java
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 把行映射成实体:

java
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;
    }
}
java
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 ​

java
jdbc.batchUpdate("INSERT INTO posts (title) VALUES (?)",
        listOfTitles.stream().map(t -> new Object[]{t}).toList());

动态 SQL(简单版) ​

Laravel 有 when() 条件拼接;JdbcTemplate 没内置,手写拼接时注意参数占位:

java
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 手拼只适合简单场景。

事务 ​

java
@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 BuilderJdbcTemplate
全表查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 时的选择。下一节:分页。

面向 PHP / Laravel 开发者的 Spring Boot 中文文档