Spring JDBC + JdbcTemplate 学习笔记(方法驱动格式)
本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍:
- 方法:方法签名/名称
- 作用:该方法解决什么问题
- 参数:主要参数的含义和类型
- 方法案例:代码示例(可直接运行)
- 实例说明:对案例的详细解释,包括注意事项
前置环境:Spring 6 + HikariCP + HSQLDB,配置了DataSource和JdbcTemplateBean。
第一章:准备工作与环境配置
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 – 插入并返回自增主键
- 作用:执行插入语句后,获取数据库自动生成的主键(如
IDENTITY或AUTO_INCREMENT)。 参数:
PreparedStatementCreator psc– 用于创建PreparedStatement,必须指定Statement.RETURN_GENERATED_KEYSKeyHolder 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_KEYS的PreparedStatement。- 执行后通过
keyHolder.getKey()获取主键,通常为Number类型,需转换为Long或Integer。 - 适用于大多数关系型数据库(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,但回调内创建的PreparedStatement和ResultSet必须由开发者手动释放(建议使用 try-with-resources)。 - 一般情况下优先使用更高级的
query()、update()等方法,只有非常规需求时才使用ConnectionCallback。
- JdbcTemplate 会自动释放
方法八: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– 指定哪些异常触发回滚(默认RuntimeException和Error)
- 方法案例:银行转账(两个更新操作必须在同一事务)
@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);
}
}实例说明:
- 需要先在配置类上添加
@EnableTransactionManagement并注册DataSourceTransactionManager。 - 默认情况下,只有
RuntimeException会触发回滚;若希望检查型异常也回滚,需设置@Transactional(rollbackFor = Exception.class)。 - 事务边界默认基于 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"第六章:常见问题与注意事项
queryForObject空结果抛异常:需捕获EmptyResultDataAccessException或改用query方法加判断。BeanPropertyRowMapper要求属性有 setter:否则映射会忽略该列。- 批量插入时数据库限制:例如 HSQLDB 默认批量最大 100 条,可分批调用
batchUpdate。 事务失效场景:
- 同一个类内非
@Transactional方法调用@Transactional方法(无代理) - 方法被
private修饰 - 异常被
try-catch吞掉且没有重新抛出
- 同一个类内非
- 使用
KeyHolder时:必须通过Statement.RETURN_GENERATED_KEYS创建PreparedStatement。
附录:JdbcTemplate 方法速查表
| 方法名 | 作用 | 适用场景 |
|---|---|---|
queryForObject | 单行,返回对象 | 根据主键查询、聚合函数 |
query | 多行,返回 List | 列表查询、分页 |
update | 单条 DML | 插入/更新/删除 |
update + KeyHolder | 插入并返回主键 | 获取自增 ID |
batchUpdate | 批量 DML | 批量导入数据 |
execute(String) | DDL/存储过程 | 建表、清空表 |
execute(ConnectionCallback) | 原生 JDBC 操作 | 特殊需求、存储过程 |
---。
评论