【Spring6笔记】 - 11 - JDBCTemplate
1、JdbcTemplate 概述与开发环境搭建
1.1 什么是 JdbcTemplate?
JdbcTemplate 是 Spring 框架对 JDBC(Java Database Connectivity)的二次封装。它的核心设计目标是:
- 简化代码:消除冗长的 JDBC 模板代码(如:注册驱动、创建连接、释放资源、异常处理等)。
- 统一风格:提供了一套一致的 API 来执行 SQL 语句(增删改查)。
- 灵活性 :虽然它简化了操作,但并没有隐藏底层的 JDBC 特性。如果需要,开发者依然可以通过回调函数(如
execute方法)直接操作PreparedStatement。 - 可集成性:它是 Spring 生态的一部分,能够完美享受 Spring 的声明式事务管理。
注意 :JdbcTemplate 并不是唯一的选择。在大型项目中,我们通常会让 Spring 集成更强大的 ORM 框架,如 MyBatis 或 Hibernate。
1.2 Maven 项目依赖配置 (pom.xml)
要使用 JdbcTemplate,除了 Spring 核心依赖外,还需要引入 spring-jdbc 模块、数据库驱动以及第三方连接池。
以下是核心依赖清单(基于 Spring 6.x):
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
1.3 数据源(DataSource)的概念与实现
在 JDBC 编程中,数据源(DataSource)是获取数据库连接(Connection)的标准工厂。
① 数据源的作用
- 资源池化:管理连接的创建与销毁,避免频繁建立 TCP 连接带来的开销。
- 接口规范 :只要实现了
javax.sql.DataSource接口的类,都可以作为 Spring 的数据源(如 Druid, C3P0, DBCP, HikariCP)。
② 自定义数据源示例
虽然生产环境使用 Druid,但通过手动实现 DataSource 接口(如你代码中的 MyDataSource),可以清晰看到数据源的核心职责是利用 DriverManager 获取连接并注入配置参数(driverClass, url, username, password)。
java
@Component("myDataSource")
public class MyDataSource implements DataSource {
/*
* 数据源存在的目的是为了提供Connection对象
* 只要实现了DataSource接口的都是数据源
* druid 连接池 c3p0 连接池 dbcp 连接池 都是实现了DataSource接口
* */
@Value("com.mysql.cj.jdbc.Driver")
private String driverClass;
@Value("jdbc:mysql://localhost:3306/spring6")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
public String getDriverClass() {
return driverClass;
}
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
try {
//注册驱动
Class.forName(driverClass);
//获取数据库连接对象
return DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
}
1.4 Spring 核心配置文件 (spring.xml)
在 Spring 中,JdbcTemplate 的初始化遵循 "依赖注入" 原则。
配置步骤解析:
- 配置数据源 (DataSource):管理连接池。
- 配置 JdbcTemplate:将数据源注入到 JdbcTemplate 实例中。
- 注解扫描 :为了方便后续使用
@Repository等注解。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myDataSource"/>
</bean>
<context:component-scan base-package="com.zzz"/>
</beans>
1.5 验证环境是否成功
通过单元测试加载配置文件并获取 JdbcTemplate 实例,若不报错且能打印出对象地址,说明环境搭建成功。
java
@Test
public void testJdbcTemplateInit() {
// 1. 创建 Spring 容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 2. 从容器中获取 JdbcTemplate 对象
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
// 3. 打印对象,验证是否成功注入 DataSource
System.out.println("JdbcTemplate 实例:" + jdbcTemplate);
}
2、使用 JdbcTemplate 完成基础 CURD 操作
在 JdbcTemplate 中,所有的写操作(增、删、改)统一使用 update() 方法,而读操作(查)则根据返回结果的不同(单条、多条、单值)使用不同的方法。
2.1 数据的写操作:新增、修改、删除
JdbcTemplate.update() 方法是执行所有 非查询 SQL 语句的核心。
① 新增数据 (Insert)
-
核心方法 :
int update(String sql, Object... args) -
代码示例:
javaString sql = "insert into t_user(real_name, age) values(?, ?)"; // 第一个参数是SQL,后续是可变参数,对应SQL中的占位符 ? int count = jdbcTemplate.update(sql, "zzz", 23); System.out.println("成功插入记录数:" + count);
② 修改数据 (Update)
-
逻辑说明 :与新增一致,只需改变 SQL 语句。通常需要带
where条件防止全表更新。 -
代码示例:
javaString sql = "update t_user set real_name = ?, age = ? where id = ?"; int count = jdbcTemplate.update(sql, "张三", 25, 1);
③ 删除数据 (Delete)
-
逻辑说明:通常通过主键 ID 进行精准删除。
-
代码示例:
javaString sql = "delete from t_user where id = ?"; int count = jdbcTemplate.update(sql, 1);
2.2 数据的查询操作:单条、多条、聚合
查询是 JdbcTemplate 最灵活的部分。为了将数据库的 ResultSet 结果集自动转换为 Java 对象,我们需要用到 RowMapper 接口。Spring 提供了默认实现类 BeanPropertyRowMapper。
① 查询单条记录 (Query One Object)
-
核心方法 :
T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) -
重点分析 :
BeanPropertyRowMapper会自动根据数据库列名 (如real_name)与实体类属性名 (如realName)进行驼峰映射。 -
代码示例:
javaString sql = "select * from t_user where id = ?"; // 注意:如果查询不到结果或查到多条结果,此方法会抛出异常 User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);
② 查询多条记录 (Query List)
-
核心方法 :
List<T> query(String sql, RowMapper<T> rowMapper, Object... args) -
代码示例:
javaString sql = "select * from t_user"; List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); // 结果为一个 List 集合,若无数据则返回空集合(长度为0)
③ 查询单列单值 (Query Single Value)
-
核心场景 :常用于聚合函数,如
count(*)、max()、sum()等。 -
核心方法 :
T queryForObject(String sql, Class<T> requiredType) -
代码示例:
javaString sql = "select count(1) from t_user"; // 第二个参数指定返回值的类型(如 Integer.class 或 Long.class) Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
2.3 实体类 (POJO) 的编写规范
在使用 BeanPropertyRowMapper 时,实体类(如 User.java)必须满足以下条件:
- 必须有无参构造方法:Spring 反射创建对象时需要。
- 属性名与数据库字段对应 :支持下划线转驼峰(例如:数据库
real_name对应 JavarealName)。 - 提供 Getter/Setter 方法:用于属性注入。
总结:
- 增/删/改 :统统找
update()。 - 查对象/查列表 :利用
BeanPropertyRowMapper自动封装。 - 查数量 :指定返回类型的
Class对象。
3、JdbcTemplate 高阶应用(批量操作与回调函数)
在处理海量数据或需要高度自定义 JDBC 过程时,简单的 update 或 query 可能不够用。Spring 提供了批量处理机制和底层回调接口。
3.1 批量操作(Batch Operations)
当需要同时插入或更新成千上万条记录时,单条执行 SQL 会频繁与数据库建立连接,导致性能低下。批量操作可以显著减少网络开销。
① 批量新增/修改/删除
- 核心方法 :
int[] batchUpdate(String sql, List<Object[]> batchArgs) - 参数解析 :
sql:带占位符的 SQL 模板。batchArgs:一个List集合,集合中的每个Object[]数组代表一行数据,数组内的元素对应 SQL 中的?。
- 返回值 :返回一个
int[]数组,数组的每个元素代表对应行受影响的行数。
② 代码实现(以批量新增为例)
java
@Test
public void testBatchInsert() {
String sql = "insert into t_user(real_name, age) values(?,?)";
// 1. 准备多组数据,每组数据是一个 Object 数组
Object[] obj1 = {"zzz1", 23};
Object[] obj2 = {"zzz2", 24};
Object[] obj3 = {"zzz3", 25};
// 2. 将数组添加到 List 集合中
List<Object[]> list = new ArrayList<>();
list.add(obj1);
list.add(obj2);
list.add(obj3);
// 3. 执行批量操作
int[] counts = jdbcTemplate.batchUpdate(sql, list);
System.out.println("批量执行结果:" + Arrays.toString(counts));
}
注意 :批量修改和批量删除的逻辑完全一致,只需更换 SQL 语句并调整
Object[]数组中的参数顺序(例如把 ID 放在最后)。
3.2 使用回调函数实现底层操作(Callback)
JdbcTemplate 屏蔽了太多底层细节,如果你偶尔想重温原始的 JDBC 编程(比如手动操作 ResultSet 或 PreparedStatement),可以使用 execute 方法配合回调接口。
① 核心接口:PreparedStatementCallback
- 作用 :允许你直接操作
PreparedStatement对象。 - 优势:在不破坏 Spring 事务管理和连接管理的前提下,获得对 JDBC 底层对象的完全控制权。
② 代码实现
java
@Test
public void testCallback() {
String sql = "select * from t_user where id = ?";
// 使用 execute 方法并传入匿名内部类
User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
@Override
public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
// 1. 手动设置参数
ps.setInt(1, 2);
// 2. 手动执行查询并处理结果集
ResultSet rs = ps.executeQuery();
User user = null;
if (rs.next()) {
user = new User(
rs.getInt("id"),
rs.getString("real_name"),
rs.getInt("age")
);
}
return user; // 此返回值将作为 jdbcTemplate.execute 的返回值
}
});
System.out.println("通过回调获取的对象:" + user);
}
3.3 数据库字段名与实体类属性名的映射
在你的代码中,数据库字段是 real_name(下划线),而 Java 属性是 realName(驼峰)。
-
BeanPropertyRowMapper 的神奇之处:
它默认支持下划线转驼峰 。只要你的数据库字段遵循规范(如
user_id),Java 类遵循规范(如userId),它就能自动匹配。 -
手动映射方案:
如果字段名完全不一致(例如数据库叫
name,Java 叫username),则不能使用BeanPropertyRowMapper,而需要实现RowMapper<User>接口并重写mapRow方法,手动调用set方法。
总结与对比
| 操作类型 | 推荐方法 | 关键点 |
|---|---|---|
| 单条增删改 | update() |
直接传参即可 |
| 批量增删改 | batchUpdate() |
使用 List<Object[]> 封装数据 |
| 查单条对象 | queryForObject() |
配合 BeanPropertyRowMapper,需处理查不到报错 |
| 查 List 集合 | query() |
配合 BeanPropertyRowMapper,查不到返回空集合 |
| 聚合查询 | queryForObject() |
第二个参数直接传 Integer.class 或 Long.class |
| 底层复杂操作 | execute() |
使用 PreparedStatementCallback 接口 |