JdbcTemplate
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

JdbcTemplate

ASN__
2026-06-03 / 0 评论 / 2 阅读 / 正在检测是否收录...

Spring JDBC + JdbcTemplate 学习笔记(方法驱动格式)

本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍:

  • 方法:方法签名/名称
  • 作用:该方法解决什么问题
  • 参数:主要参数的含义和类型
  • 方法案例:代码示例(可直接运行)
  • 实例说明:对案例的详细解释,包括注意事项
前置环境:Spring 6 + HikariCP + HSQLDB,配置了 DataSourceJdbcTemplate Bean。

第一章:准备工作与环境配置

1.1 Maven 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.0</version>
</dependency>
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.7.1</version>
</dependency>

1.2 Spring 配置类(AppConfig)

@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {
    @Value("${jdbc.url}") String jdbcUrl;
    @Value("${jdbc.username}") String jdbcUsername;
    @Value("${jdbc.password}") String jdbcPassword;

    @Bean
    DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(jdbcUrl);
        config.setUsername(jdbcUsername);
        config.setPassword(jdbcPassword);
        return new HikariDataSource(config);
    }

    @Bean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

1.3 数据库初始化(自动建表)

@Component
public class DatabaseInitializer {
    @Autowired JdbcTemplate jdbcTemplate;
    @PostConstruct
    void init() {
        jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users ("
            + "id BIGINT IDENTITY PRIMARY KEY, "
            + "email VARCHAR(100) NOT NULL UNIQUE, "
            + "password VARCHAR(100), "
            + "name VARCHAR(100))");
    }
}

第二章:核心方法详解(按格式组织)

方法一:queryForObject() – 查询单行记录并映射为对象

  • 作用:执行查询 SQL,期望返回一条记录,并将结果集的第一行通过 RowMapper 转换成 Java 对象。若查询结果为空或大于一行则抛出异常。
  • 参数

    • String sql – SQL 语句(可使用 ? 占位符)
    • RowMapper<T> rowMapper – 将 ResultSet 当前行映射为 T 对象的函数式接口
    • Object... args – 可变参数,按顺序替换 SQL 中的 ?
  • 方法案例:根据 email 查询用户
public User getUserByEmail(String email) {
    String sql = "SELECT id, email, password, name FROM users WHERE email = ?";
    return jdbcTemplate.queryForObject(sql,
        (rs, rowNum) -> new User(
            rs.getLong("id"),
            rs.getString("email"),
            rs.getString("password"),
            rs.getString("name")
        ),
        email
    );
}
  • 实例说明

    • RowMapper 的 lambda 表达式接收 ResultSet 和当前行号(通常忽略行号),手动构建 User 对象。
    • 如果数据库中不存在该 email,会抛出 EmptyResultDataAccessException,需要捕获处理或改为返回 null
    • 也适用于聚合查询,例如统计总数:
public long countUsers() {
    String sql = "SELECT COUNT(*) FROM users";
    return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getLong(1));
}

方法二:query() – 查询多行记录,返回 List

  • 作用:执行查询 SQL,将结果集中的每一行都通过 RowMapper 转换为对象,最后返回 List<T>。结果集为空时返回空列表(不抛异常)。
  • 参数

    • String sql – SQL 语句
    • RowMapper<T> rowMapper – 行映射器
    • Object... args – 参数列表
  • 方法案例:分页查询用户
public List<User> getUsersByPage(int pageIndex, int pageSize) {
    String sql = "SELECT * FROM users LIMIT ? OFFSET ?";
    int offset = (pageIndex - 1) * pageSize;
    return jdbcTemplate.query(sql,
        (rs, rowNum) -> new User(
            rs.getLong("id"),
            rs.getString("email"),
            rs.getString("password"),
            rs.getString("name")
        ),
        pageSize, offset
    );
}
  • 实例说明

    • 使用 BeanPropertyRowMapper 可自动映射(要求列名与属性名一致或下划线转驼峰):
public List<User> getAllUsers() {
    return jdbcTemplate.query("SELECT * FROM users", 
        new BeanPropertyRowMapper<>(User.class));
}
  • BeanPropertyRowMapper 适合字段较多的简单映射;复杂映射仍需手动 RowMapper

方法三:update() – 执行插入、更新、删除

  • 作用:执行 DML 语句(INSERT/UPDATE/DELETE),返回受影响的行数。
  • 参数

    • String sql – DML 语句
    • Object... args – 参数列表
  • 返回值int – 受影响的行数
  • 方法案例 1:更新用户名称
public int updateUserName(Long id, String newName) {
    String sql = "UPDATE users SET name = ? WHERE id = ?";
    return jdbcTemplate.update(sql, newName, id);
}
  • 实例说明:调用方可根据返回值判断是否更新成功(例如返回值 == 0 表示用户不存在)。
  • 方法案例 2:删除用户
public boolean deleteUser(Long id) {
    String sql = "DELETE FROM users WHERE id = ?";
    int rows = jdbcTemplate.update(sql, id);
    return rows > 0;
}
  • 方法案例 3:插入用户(不返回主键)
public void addUserWithoutKey(String name, String email) {
    String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
    jdbcTemplate.update(sql, name, email);
}

方法四:update() + KeyHolder – 插入并返回自增主键

  • 作用:执行插入语句后,获取数据库自动生成的主键(如 IDENTITYAUTO_INCREMENT)。
  • 参数

    • PreparedStatementCreator psc – 用于创建 PreparedStatement,必须指定 Statement.RETURN_GENERATED_KEYS
    • KeyHolder keyHolder – 用于接收生成的主键
  • 方法案例
public User addUserAndGetId(String name, String email, String password) {
    String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)";
    KeyHolder keyHolder = new GeneratedKeyHolder();
    
    jdbcTemplate.update(connection -> {
        PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        ps.setString(1, name);
        ps.setString(2, email);
        ps.setString(3, password);
        return ps;
    }, keyHolder);
    
    Long newId = keyHolder.getKey().longValue();
    return new User(newId, email, password, name);
}
  • 实例说明

    • PreparedStatementCreator 是一个函数式接口,需手动设置参数并返回带 RETURN_GENERATED_KEYSPreparedStatement
    • 执行后通过 keyHolder.getKey() 获取主键,通常为 Number 类型,需转换为 LongInteger
    • 适用于大多数关系型数据库(MySQL、HSQLDB、PostgreSQL、SQL Server 等)。

方法五:batchUpdate() – 批量操作

  • 作用:一次性执行多条相同结构的 SQL(通常是批量插入或批量更新),性能远高于循环单条执行。
  • 参数

    • String sql – 带占位符的 SQL 模板
    • List<Object[]> batchArgs – 每个元素是一个 Object[],代表一次执行对应的参数
  • 返回值int[] – 数组长度等于批处理次数,每个元素为对应操作影响的行数
  • 方法案例:批量插入多个用户
public int[] batchAddUsers(List<User> users) {
    String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)";
    List<Object[]> batch = new ArrayList<>();
    for (User u : users) {
        batch.add(new Object[]{u.getName(), u.getEmail(), u.getPassword()});
    }
    return jdbcTemplate.batchUpdate(sql, batch);
}
  • 实例说明

    • 批量操作会减少 JDBC 与数据库之间的网络往返次数,显著提升吞吐量。
    • 注意:某些数据库(如 HSQLDB)对单次批处理的大小有限制,可分批提交。

方法六:execute() – 执行任意 SQL(DDL 或存储过程)

  • 作用:执行 DDL(如 CREATE TABLE)或任何不希望返回结果集的 SQL,也可以用于执行存储过程。
  • 参数

    • String sql – 要执行的 SQL 字符串
  • 返回值:无(或者可重载为 execute(Callback) 形式返回自定义结果)
  • 方法案例:创建日志表
public void createLogTable() {
    jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sys_log ("
        + "id INT PRIMARY KEY, "
        + "message VARCHAR(255), "
        + "create_time TIMESTAMP)");
}
  • 实例说明execute(String sql) 不关心返回结果,适合做数据库初始化、清空表、重建索引等管理操作。

方法七:execute(ConnectionCallback) – 最底层扩展

  • 作用:获取原生 JDBC Connection 对象,执行任意复杂数据库操作(如设置超时、调用存储过程、使用原生 API 等)。
  • 参数

    • ConnectionCallback<T> action – 回调接口,接收 Connection 并返回 T
  • 返回值:由回调决定的 T
  • 方法案例:使用原生 PreparedStatement 和手动控制事务
public User getUserWithNativeConnection(Long id) {
    return jdbcTemplate.execute((Connection conn) -> {
        try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    return new User(
                        rs.getLong("id"),
                        rs.getString("email"),
                        rs.getString("password"),
                        rs.getString("name")
                    );
                }
                return null;
            }
        }
    });
}
  • 实例说明

    • JdbcTemplate 会自动释放 Connection,但回调内创建的 PreparedStatementResultSet 必须由开发者手动释放(建议使用 try-with-resources)。
    • 一般情况下优先使用更高级的 query()update() 等方法,只有非常规需求时才使用 ConnectionCallback

方法八:execute(PreparedStatementCallback) – 灵活处理 PreparedStatement

  • 作用:由 JdbcTemplate 创建 PreparedStatement,回调中可自行执行查询、更新并处理结果集。
  • 参数

    • String sql – SQL 模板
    • PreparedStatementCallback<T> action – 回调接收 PreparedStatement,返回 T
  • 方法案例:判断用户是否存在
public boolean existsUserByName(String name) {
    return jdbcTemplate.execute("SELECT 1 FROM users WHERE name = ?",
        (PreparedStatement ps) -> {
            ps.setString(1, name);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next();   // 有记录返回 true
            }
        });
}
  • 实例说明:此方法比 ConnectionCallback 更高层,无需创建 PreparedStatement,但依然需要手动执行查询和处理 ResultSet

第三章:RowMapper 详解(两个常用实现)

3.1 手动 RowMapper(Lambda)

  • 作用:将 ResultSet 当前行的数据提取并组装成业务对象。
  • 格式(ResultSet rs, int rowNum) -> { ... }
  • 案例:已在 queryForObject 中展示。

3.2 BeanPropertyRowMapper

  • 作用:自动将结果集的列名(支持下划线转驼峰)映射到 JavaBean 的同名属性(需有 setter)。
  • 参数BeanPropertyRowMapper.newInstance(Class<T>) 或构造函数。
  • 案例
List<User> users = jdbcTemplate.query("SELECT id, email, name FROM users", 
    BeanPropertyRowMapper.newInstance(User.class));
  • 实例说明:要求数据库列名 email 对应 Java 属性 email;若列名为 user_name,则对应 userName。非常便捷,适合大多数查询场景。

第四章:声明式事务管理

核心注解:@Transactional

  • 作用:将方法(或类中的所有方法)标记为事务性方法。Spring 会在方法执行前开启事务,执行成功则提交,抛出未捕获异常则回滚。
  • 参数(常用)

    • propagation – 传播行为(默认 REQUIRED
    • isolation – 隔离级别(默认数据库默认级别)
    • rollbackFor – 指定哪些异常触发回滚(默认 RuntimeExceptionError
  • 方法案例:银行转账(两个更新操作必须在同一事务)
@Service
public class AccountService {
    @Autowired JdbcTemplate jdbcTemplate;

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
        // 模拟异常:如果转账金额过大,回滚两个更新
        if (amount.compareTo(BigDecimal.valueOf(10000)) > 0) {
            throw new RuntimeException("转账金额超过限额");
        }
        jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
    }
}
  • 实例说明

    1. 需要先在配置类上添加 @EnableTransactionManagement 并注册 DataSourceTransactionManager
    2. 默认情况下,只有 RuntimeException 会触发回滚;若希望检查型异常也回滚,需设置 @Transactional(rollbackFor = Exception.class)
    3. 事务边界默认基于 AOP 代理,内部方法调用(this.method())不会触发事务。

第五章:实战示例 – RESTful API 完整流程

5.1 User 实体

public class User {
    private Long id;
    private String name;
    private String email;
    private String password;
    // getter/setter/构造方法 省略
}

5.2 UserService(使用上述方法)

@Service
public class UserService {
    @Autowired JdbcTemplate jdbcTemplate;

    public User addUser(String name, String email, String pwd) {
        KeyHolder kh = new GeneratedKeyHolder();
        jdbcTemplate.update(conn -> {
            PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO users (name, email, password) VALUES (?,?,?)",
                Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, name); ps.setString(2, email); ps.setString(3, pwd);
            return ps;
        }, kh);
        return new User(kh.getKey().longValue(), email, pwd, name);
    }

    public User getUser(Long id) {
        try {
            return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?",
                BeanPropertyRowMapper.newInstance(User.class), id);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public List<User> listUsers() {
        return jdbcTemplate.query("SELECT * FROM users", BeanPropertyRowMapper.newInstance(User.class));
    }

    public int updateUser(Long id, String newName) {
        return jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id);
    }

    public int deleteUser(Long id) {
        return jdbcTemplate.update("DELETE FROM users WHERE id = ?", id);
    }
}

5.3 UserController

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired UserService userService;

    @PostMapping
    public User add(@RequestParam String name, @RequestParam String email, @RequestParam String pwd) {
        return userService.addUser(name, email, pwd);
    }

    @GetMapping("/{id}")
    public User get(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @GetMapping
    public List<User> list() {
        return userService.listUsers();
    }

    @PutMapping("/{id}")
    public String update(@PathVariable Long id, @RequestParam String name) {
        int rows = userService.updateUser(id, name);
        return rows > 0 ? "success" : "user not found";
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        int rows = userService.deleteUser(id);
        return rows > 0 ? "deleted" : "not exist";
    }
}

5.4 测试命令

# 新增
curl -X POST "http://localhost:8080/api/users?name=张三&email=zs@test.com&pwd=123"

# 查询单个
curl "http://localhost:8080/api/users/1"

# 查询全部
curl "http://localhost:8080/api/users"

# 更新
curl -X PUT "http://localhost:8080/api/users/1?name=张三丰"

# 删除
curl -X DELETE "http://localhost:8080/api/users/1"

第六章:常见问题与注意事项

  1. queryForObject 空结果抛异常:需捕获 EmptyResultDataAccessException 或改用 query 方法加判断。
  2. BeanPropertyRowMapper 要求属性有 setter:否则映射会忽略该列。
  3. 批量插入时数据库限制:例如 HSQLDB 默认批量最大 100 条,可分批调用 batchUpdate
  4. 事务失效场景

    • 同一个类内非 @Transactional 方法调用 @Transactional 方法(无代理)
    • 方法被 private 修饰
    • 异常被 try-catch 吞掉且没有重新抛出
  5. 使用 KeyHolder:必须通过 Statement.RETURN_GENERATED_KEYS 创建 PreparedStatement

附录:JdbcTemplate 方法速查表

方法名作用适用场景
queryForObject单行,返回对象根据主键查询、聚合函数
query多行,返回 List列表查询、分页
update单条 DML插入/更新/删除
update + KeyHolder插入并返回主键获取自增 ID
batchUpdate批量 DML批量导入数据
execute(String)DDL/存储过程建表、清空表
execute(ConnectionCallback)原生 JDBC 操作特殊需求、存储过程

---。

0

评论

博主关闭了所有页面的评论