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),需配合 NamedParameterJdbcTemplate(JdbcTemplate 的子类)。
示例:使用命名参数查询
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("系统异常,请稍后重试");
}
}
五、注意事项
-
SQL 注入防护:
- 始终使用 占位符(
?或命名参数) 传递参数,避免直接拼接 SQL 字符串。 - 错误示例:
String sql = "SELECT * FROM user WHERE name = '" + name + "'";(存在注入风险)
- 始终使用 占位符(
-
资源释放:
JdbcTemplate自动管理连接、Statement、ResultSet 的释放,无需手动关闭。
-
性能优化:
- 批量操作优先使用
batchUpdate(),减少数据库交互次数。 - 大结果集使用
RowCallbackHandler流式处理,避免一次性加载所有数据到内存。 - 生产环境使用 连接池 (如 HikariCP),而非
DriverManagerDataSource(无连接池,性能差)。
- 批量操作优先使用
-
类型转换:
-
JdbcTemplate自动处理常见类型转换(如String↔VARCHAR、Integer↔INT)。 -
对于复杂类型(如
LocalDateTime),需手动指定类型或使用SqlParameterValue:javaimport 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();
}
}
}