JdbcTemplate常用方法

JdbcTemplate 是 Spring Framework 中用于简化 JDBC 操作的核心类。它封装了 JDBC 的底层细节,如连接管理、Statement 创建、异常处理等,让开发者能够更专注于 SQL 语句和业务逻辑,从而提高开发效率并减少模板代码。

SpringBoot配置

1. 依赖配置(Maven)

需引入 Spring JDBC数据库驱动(以 MySQL 为例):

xml 复制代码
<!-- Spring JDBC 核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.20</version> <!-- 与 Spring 版本一致 -->
</dependency>

<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
    <scope>runtime</scope>
</dependency>

<!-- Spring 上下文(用于依赖注入) -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>

接着就是在application.yml配置数据源。

jdbcTemplate常用的方法

初始化一个数据库demo

mysql 复制代码
CREATE TABLE `user` ( 
`id` INT PRIMARY KEY AUTO_INCREMENT, 
`name` VARCHAR(50) NOT NULL, 
`age` INT DEFAULT NULL, 
`email` VARCHAR(100) UNIQUE );

INSERT(增)

JdbcTemplate 提供 update() 方法执行增删改操作,返回受影响的行数。

示例 1:简单新增(使用 ? 占位符)
java 复制代码
public int addUser(String name, Integer age, String email) {
    String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
    // 参数顺序需与 SQL 占位符一致
    return jdbcTemplate.update(sql, name, age, email);
}
示例 2:新增并返回自增主键(KeyHolder

如果需要获取插入后的自增 ID(如 id),可使用 KeyHolder

java 复制代码
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import java.sql.PreparedStatement;

public int addUserWithId(String name, Integer age, String email) {
    String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
    
    KeyHolder keyHolder = new GeneratedKeyHolder(); // 用于存储自增主键
    
    jdbcTemplate.update(connection -> {
        PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); // 指定返回的主键列名
        ps.setString(1, name);
        ps.setObject(2, age); // 支持 null
        ps.setString(3, email);
        return ps;
    }, keyHolder);
    
    // 获取自增主键(Long 类型,需根据数据库类型转换)
    return keyHolder.getKey().intValue();
}

UPDATE(改)

java 复制代码
public int updateUserAge(Integer id, Integer newAge) { 
    String sql = "UPDATE user SET age = ? WHERE id = ?"; 
    return jdbcTemplate.update(sql, newAge, id); 
}

删除(DELETE)

java 复制代码
public int deleteUser(Integer id) {
    String sql = "DELETE FROM user WHERE id = ?";
    return jdbcTemplate.update(sql, id);
}

查询(SELECT)

查询是 JdbcTemplate 最常用的场景,提供多种方法适配不同结果集:

场景 1:查询单个对象(返回自定义 POJO)

需实现 RowMapper 接口,将 ResultSet 映射为 Java 对象。

java 复制代码
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private String email;

    // 构造器、getter/setter、toString()
}

步骤 2:实现 RowMapper

java 复制代码
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserRowMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        user.setAge(rs.getInt("age"));
        user.setEmail(rs.getString("email"));
        return user;
    }
}

步骤 3:查询单个对象

java 复制代码
public User getUserById(Integer id) {
    String sql = "SELECT id, name, age, email FROM user WHERE id = ?";
    // queryForObject():返回单个对象,无结果时抛出 EmptyResultDataAccessException
    return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}
场景 2:查询多个对象(返回 List<POJO>
java 复制代码
public List<User> getAllUsers() {
    String sql = "SELECT id, name, age, email FROM user";
    return jdbcTemplate.query(sql, new UserRowMapper());
}
场景 3:查询单个值(如 count、sum)
java 复制代码
public int getUserCount() {
    String sql = "SELECT COUNT(*) FROM user";
    // queryForObject():指定返回类型(如 Integer.class)
    return jdbcTemplate.queryForObject(sql, Integer.class);
}

public String getUserNameById(Integer id) {
    String sql = "SELECT name FROM user WHERE id = ?";
    return jdbcTemplate.queryForObject(sql, String.class, id);
}
场景 4:查询结果映射为 Map

适用于不需要自定义 POJO 的简单场景,Map 的 key 为列名,value 为字段值:

java 复制代码
public Map<String, Object> getUserMapById(Integer id) {
    String sql = "SELECT id, name, age, email FROM user WHERE id = ?";
    return jdbcTemplate.queryForMap(sql, id);
}
场景 5:查询大结果集(分页 / 流式处理)

如果结果集过大(如 10 万条以上),直接查询会占用大量内存,推荐使用 query() 结合 RowCallbackHandler 进行流式处理:

java 复制代码
import org.springframework.jdbc.core.RowCallbackHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public List<User> getUserByPage(int pageNum, int pageSize) {
    int offset = (pageNum - 1) * pageSize;
    String sql = "SELECT id, name, age, email FROM user LIMIT ? OFFSET ?";

    List<User> userList = new ArrayList<>();
    
    jdbcTemplate.query(sql, new RowCallbackHandler() {
        @Override
        public void processRow(ResultSet rs) throws SQLException {
            User user = new User();
            user.setId(rs.getInt("id"));
            user.setName(rs.getString("name"));
            user.setAge(rs.getInt("age"));
            user.setEmail(rs.getString("email"));
            userList.add(user);
        }
    }, pageSize, offset);

    return userList;
}

四、高级特性

1. 批量操作(Batch Update)

用于一次性执行多个增删改操作,提升性能(减少数据库连接次数)。

示例:批量新增用户
java 复制代码
public int[] batchAddUsers(List<User> userList) {
    String sql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";

    // 转换为 Object 数组列表(每个数组对应一条 SQL 的参数)
    List<Object[]> batchArgs = new ArrayList<>();
    for (User user : userList) {
        batchArgs.add(new Object[]{user.getName(), user.getAge(), user.getEmail()});
    }

    // batchUpdate():返回每个操作受影响的行数数组
    return jdbcTemplate.batchUpdate(sql, batchArgs);
}

2. 命名参数(Named Parameters)

默认 JdbcTemplate 使用 ? 占位符,参数顺序必须严格对应。若需使用命名参数(如 :name),需配合 NamedParameterJdbcTemplateJdbcTemplate 的子类)。

示例:使用命名参数查询
java 复制代码
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.Map;

@Service
public class UserService {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate; // 注入命名参数模板

    public User getUserByName(String name) {
        String sql = "SELECT id, name, age, email FROM user WHERE name = :name";
        
        // 封装参数(key 对应命名参数,value 为参数值)
        Map<String, Object> params = new HashMap<>();
        params.put("name", name);
        
        // queryForObject():使用命名参数,无需关心参数顺序
        return namedParameterJdbcTemplate.queryForObject(sql, params, new UserRowMapper());
    }
}

3. 事务管理

JdbcTemplate 本身不管理事务,需结合 Spring 的事务管理机制(如 @Transactional 注解)。

示例:事务控制下的批量操作
java 复制代码
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 声明事务:方法内所有操作要么全部成功,要么全部回滚
    @Transactional
    public void batchOperation(List<User> addUsers, List<Integer> deleteIds) {
        // 批量新增
        String addSql = "INSERT INTO user (name, age, email) VALUES (?, ?, ?)";
        List<Object[]> addArgs = new ArrayList<>();
        for (User user : addUsers) {
            addArgs.add(new Object[]{user.getName(), user.getAge(), user.getEmail()});
        }
        jdbcTemplate.batchUpdate(addSql, addArgs);

        // 批量删除(若此处抛出异常,新增操作会回滚)
        String deleteSql = "DELETE FROM user WHERE id = ?";
        List<Object[]> deleteArgs = new ArrayList<>();
        for (Integer id : deleteIds) {
            deleteArgs.add(new Object[]{id});
        }
        jdbcTemplate.batchUpdate(deleteSql, deleteArgs);
    }
}

注意 :需在配置类中启用事务管理(@EnableTransactionManagement)。

4. 异常处理

JdbcTemplate 将 JDBC 异常(如 SQLException)转换为 Spring 统一的 数据访问异常体系 (继承自 DataAccessException),常用异常包括:

  • EmptyResultDataAccessException:查询无结果
  • IncorrectResultSizeDataAccessException:查询结果数量不匹配
  • DuplicateKeyException:主键冲突
  • DataIntegrityViolationException:数据完整性约束 violation
示例:异常捕获
java 复制代码
public User getUserByIdSafe(Integer id) {
    try {
        String sql = "SELECT id, name, age, email FROM user WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
    } catch (EmptyResultDataAccessException e) {
        // 无结果时返回 null 或抛出自定义异常
        log.warn("用户 id={} 不存在", id);
        return null;
    } catch (DataAccessException e) {
        // 其他数据库异常(如连接失败、SQL 语法错误)
        log.error("查询用户失败:", e);
        throw new RuntimeException("系统异常,请稍后重试");
    }
}

五、注意事项

  1. SQL 注入防护

    • 始终使用 占位符(? 或命名参数) 传递参数,避免直接拼接 SQL 字符串。
    • 错误示例:String sql = "SELECT * FROM user WHERE name = '" + name + "'";(存在注入风险)
  2. 资源释放

    • JdbcTemplate 自动管理连接、Statement、ResultSet 的释放,无需手动关闭。
  3. 性能优化

    • 批量操作优先使用 batchUpdate(),减少数据库交互次数。
    • 大结果集使用 RowCallbackHandler 流式处理,避免一次性加载所有数据到内存。
    • 生产环境使用 连接池 (如 HikariCP),而非 DriverManagerDataSource(无连接池,性能差)。
  4. 类型转换

    • JdbcTemplate 自动处理常见类型转换(如 StringVARCHARIntegerINT)。

    • 对于复杂类型(如 LocalDateTime),需手动指定类型或使用 SqlParameterValue

      java 复制代码
      import org.springframework.jdbc.core.SqlParameterValue;
      import java.sql.Types;
      import java.time.LocalDateTime;
      
      public void addUserWithTime(String name, LocalDateTime createTime) {
          String sql = "INSERT INTO user (name, create_time) VALUES (?, ?)";
          // 指定参数类型为 TIMESTAMP
          SqlParameterValue timeParam = new SqlParameterValue(Types.TIMESTAMP, createTime);
          jdbcTemplate.update(sql, name, timeParam);
      }

chatGPT给的优雅代码

java 复制代码
// Java
package com.example.demo;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.*;

@Slf4j
@Repository
@RequiredArgsConstructor
public class DemoJdbcTemplateUsage {

 // 构造器注入,和你的 MysqlService 相同写法
 private final JdbcTemplate jdbcTemplate;

 // 1) 查询列表(等值条件 + 分页),返回 List<Map<String,Object>>
 public List<Map<String, Object>> listByStatus(String status, int limit, int offset) {
     int safeLimit = (limit > 0 && limit <= 1000) ? limit : 100;
     int safeOffset = Math.max(offset, 0);
     String sql = "SELECT `id`,`username`,`email` FROM `user` " +
                  "WHERE `status` = ? ORDER BY `id` DESC LIMIT ? OFFSET ?";
     return jdbcTemplate.queryForList(sql, status, safeLimit, safeOffset);
 }

 // 2) 统计总数(与列表查询的 where 参数一致)
 public int countByStatus(String status) {
     String sql = "SELECT COUNT(1) FROM `user` WHERE `status` = ?";
     Integer n = jdbcTemplate.queryForObject(sql, Integer.class, status);
     return n == null ? 0 : n;
 }

 // 3) 查询单条并映射为对象(RowMapper)
 public Optional<User> findById(long id) {
     String sql = "SELECT `id`,`username`,`email`,`status` FROM `user` WHERE `id` = ?";
     List<User> list = jdbcTemplate.query(sql, new Object[]{id}, userRowMapper());
     return list.stream().findFirst();
 }

 private RowMapper<User> userRowMapper() {
     return (rs, rowNum) -> new User(
         rs.getLong("id"),
         rs.getString("username"),
         rs.getString("email"),
         rs.getString("status")
     );
 }

 // 4) 新增并返回自增主键
 public long insertUser(String username, String email, String status) {
     String sql = "INSERT INTO `user`(`username`,`email`,`status`) VALUES(?,?,?)";
     KeyHolder kh = new GeneratedKeyHolder();
     jdbcTemplate.update(con -> {
         PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
         ps.setString(1, username);
         ps.setString(2, email);
         ps.setString(3, status);
         return ps;
     }, kh);
     Number key = kh.getKey();
     return key == null ? 0L : key.longValue();
 }

 // 5) 更新或删除(受影响行数)
 public int deactivateUser(long id) {
     String sql = "UPDATE `user` SET `status` = ? WHERE `id` = ?";
     return jdbcTemplate.update(sql, "INACTIVE", id);
 }

 // 6) 批量更新(占位符参数化,避免拼接)
 public int[] batchDeactivate(List<Long> ids) {
     String sql = "UPDATE `user` SET `status` = 'INACTIVE' WHERE `id` = ?";
     return jdbcTemplate.batchUpdate(sql, ids, ids.size(),
             (ps, userId) -> ps.setLong(1, userId));
 }

 // 7) 事务示例(多条语句要么都成功,要么都回滚)
 @Transactional
 public void transferDemo(long fromUserId, long toUserId, int points) {
     String dec = "UPDATE `user_points` SET `points` = `points` - ? WHERE `user_id` = ?";
     String inc = "UPDATE `user_points` SET `points` = `points` + ? WHERE `user_id` = ?";
     jdbcTemplate.update(dec, points, fromUserId);
     // 任意异常都会触发回滚
     jdbcTemplate.update(inc, points, toUserId);
 }

 // 8) 安全构造简单 ORDER BY(列名白名单校验)
 public List<Map<String, Object>> listWithOrder(Set<String> allowedCols, String orderBy) {
     String orderSql = "";
     if (orderBy != null && !orderBy.isBlank()) {
         String[] parts = orderBy.trim().split("\s+");
         String col = parts[0];
         String dir = (parts.length > 1 ? parts[1] : "ASC").toUpperCase(Locale.ROOT);
         if (allowedCols.contains(col) && ("ASC".equals(dir) || "DESC".equals(dir))) {
             orderSql = " ORDER BY `" + col + "` " + dir + " ";
         }
     }
     String sql = "SELECT `id`,`username` FROM `user` WHERE 1=1 " + orderSql + " LIMIT ? OFFSET ?";
     return jdbcTemplate.queryForList(sql, 100, 0);
 }

 // 9) 基本异常处理(与你的 MysqlService 相同思路)
 public List<Map<String, Object>> safeQuery() {
     try {
         return jdbcTemplate.queryForList("SELECT 1 AS one");
     } catch (DataAccessException dae) {
         log.error("DB error", dae);
         return Collections.emptyList();
     }
 }
}
相关推荐
yunxi_052 小时前
分布式文件服务实战稿:从本地存储到对象存储的架构升级
后端·面试
Chan162 小时前
【 Java八股文面试 | Redis篇 缓存问题、持久化、分布式锁 】
java·数据库·redis·后端·spring·缓存·面试
q***47182 小时前
Spring Boot 整合 Druid 并开启监控
java·spring boot·后端
bagadesu3 小时前
MySQL----case的用法
java·后端
百***58144 小时前
Spring Boot 2.7.x 至 2.7.18 及更旧的版本,漏洞说明
java·spring boot·后端
q***38514 小时前
Spring boot启动原理及相关组件
数据库·spring boot·后端
q***04055 小时前
Spring Boot项目中解决跨域问题(四种方式)
spring boot·后端·dubbo
q***64975 小时前
Spring BOOT 启动参数
java·spring boot·后端
百***62855 小时前
Spring Boot 3.X:Unable to connect to Redis错误记录
spring boot·redis·后端