数据访问(一):JdbcTemplate

依赖与驱动

spring-boot-starter-jdbc 自带 HikariCP 连接池,并为单数据源自动配置 DataSourceJdbcTemplate。还要按数据库加驱动,本教程用 H2 内存库便于直接运行:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

生产换 MySQL 用 com.mysql:mysql-connector-j、PostgreSQL 用 org.postgresql:postgresql(版本由 Boot 依赖管理锁定)。

数据源配置

spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL
spring.datasource.username=sa
spring.datasource.password=

driver-class-name 一般可省略,Boot 按 URL 推断。连接池默认 HikariCP,池参数用 spring.datasource.hikari.* 调整,完整清单以官方文档为准。

建表

schema.sql 放进 src/main/resources,默认对嵌入式数据库自动执行(非嵌入式库需 spring.sql.init.mode=always,细节以官方文档为准):

CREATE TABLE IF NOT EXISTS t_user (
    id   BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    age  INT
);

CRUD 示例

@Repository
public class UserDao {

    private final JdbcTemplate jdbc;

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

    public int insert(String name, int age) {
        return jdbc.update("insert into t_user(name, age) values (?, ?)", name, age);
    }

    public int updateAge(Long id, int age) {
        return jdbc.update("update t_user set age = ? where id = ?", age, id);
    }

    public int deleteById(Long id) {
        return jdbc.update("delete from t_user where id = ?", id);
    }

    public User findById(Long id) {
        return jdbc.queryForObject("select * from t_user where id = ?",
                (rs, n) -> new User(rs.getLong("id"), rs.getString("name"), rs.getInt("age")), id);
    }

    public List<User> findByName(String name) {
        return jdbc.query("select * from t_user where name = ?",
                (rs, n) -> new User(rs.getLong("id"), rs.getString("name"), rs.getInt("age")), name);
    }
}

要点:

  • update 执行增删改,返回受影响行数;
  • query/queryForObject 需要 RowMapper,可用 lambda (rs, rowNum) -> ... 手写映射;
  • 单行查询可能查不到时,用 query(...).stream().findFirst() 代替 queryForObject,避免 EmptyResultDataAccessException

命名参数

SQL 里用 :name 而不是 ?,配合 Boot 同样自动配置的 NamedParameterJdbcTemplate

named.query("select * from t_user where name = :name and age >= :minAge",
        Map.of("name", name, "minAge", 18),
        (rs, n) -> new User(rs.getLong("id"), rs.getString("name"), rs.getInt("age")));

参数多、可读性要求高时推荐用它。实体字段与命名参数同名时,也可用 BeanPropertySqlParameterSource 直接把对象属性当参数。

JdbcClient:更现代的写法

Spring Framework 6.1 起提供流式 API JdbcClient(Boot 3.2 起为单数据源自动提供,4.x 沿用,以官方文档为准):

User user = jdbcClient.sql("select * from t_user where id = :id")
        .param("id", id)
        .query((rs, n) -> new User(rs.getLong("id"), rs.getString("name"), rs.getInt("age")))
        .single();

小结

JdbcTemplate 是 SQL 直写、结果手工映射的传统方案,依赖少、可控性强。需要对象关系映射与大量样板 CRUD 时见 15 章 Spring Data JPA;需要 SQL 完全可控的复杂查询时见 16 章 MyBatis。

笔记加载中…