什么是 JdbcTemplate

Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作。

JdbcTemplate 是 Spring 框架 提供的一个核心 JDBC 工具类,位于 org.springframework.jdbc.core 包中。它简化了 JDBC 数据库操作,避免了繁琐的 JDBC 代码编写,提高了开发效率。

在Spring使用JDBC,首先我们通过IoC容器创建并管理一个DataSource实例,然后,Spring提供了一个JdbcTemplate,可以方便地让我们操作JDBC,因此,通常情况下,我们会实例化一个JdbcTemplate。顾名思义,这个类主要使用了Template模式

作用:

  1. 执行 SQL 语句:JdbcTemplate 提供了多种方法来执行 SQL 语句,包括查询、更新、插入、删除等。
  2. 处理结果集:JdbcTemplate 可以自动将查询结果转换为 Java 对象,简化了结果集的处理。
  3. 管理数据库连接:JdbcTemplate 自动管理数据库连接的获取和释放,避免了资源泄漏。
  4. 处理异常:JdbcTemplate 将 JDBC 异常转换为 Spring 的统一数据访问异常,方便进行异常处理。
  5. 支持事务:JdbcTemplate 可以与 Spring 的事务管理机制集成,实现事务控制。

Java程序使用JDBC接口访问关系数据库的时候,需要以下几步:

  • 创建全局DataSource实例,表示数据库连接池;
  • 在需要读写数据库的方法内部,按如下步骤访问数据库:
    • 从全局DataSource实例获取Connection实例;
    • 通过Connection实例创建PreparedStatement实例;
    • 执行SQL语句,如果是查询,则通过ResultSet读取结果集,如果是修改,则获得int结果。

正确编写JDBC代码的关键是使用try ... finally释放资源,涉及到事务的代码需要正确提交或回滚事务。

实现CURD 增删改查

我们可以将 JdbcTemplate 的创建权交给 Spring,将数据源 DataSource 的创建权也交给 Spring

在Spring容器内部将数据源 DataSource 注入到 JdbcTemplate 模版对象中,然后通过 Spring 容器获得 JdbcTemplate 对象来执行操作

准备工作

新建子模块,引入如下依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

创建 jdbc.properties,配置数据库连接池

将数据库的连接信息抽取到外部配置文件中,和spring的配置文件分离开,有利于后期维护

1
2
3
4
5
6
spring.application.name=spring6-jdbc

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/SpringJDBCTemple?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
spring.datasource.username=
spring.datasource.password=

配置 Spring 的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 开启组件扫描 -->
<context:component-scan base-package="edu.software.ergoutree.spring6jdbc"/>

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:application.properties"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${spring.datasource.driver-class-name}"/>
<property name="jdbcUrl" value="${spring.datasource.url}"/>
<property name="username" value="${spring.datasource.username}"/>
<property name="password" value="${spring.datasource.password}"/>
</bean>

<!-- 配置JdbcTemplate, 注入 DataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

</beans>

创建需要连接的数据库

1
2
3
4
5
6
7
8
9
CREATE DATABASE IF NOT EXISTS SpringJDBCTemple;
USE SpringJDBCTemple;

CREATE TABLE IF NOT EXISTS t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package edu.software.ergoutree.spring6jdbc;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class Spring6JdbcApplicationTests {

@Test
public void testJdbcTemplate() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
//通过接口来获取数据源对象
DataSource dataSource = ioc.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();

System.out.println("dataSource's class = " + dataSource.getConnection());
System.out.println("connection = " + connection);

connection.close();
}
}

通过 JDBCTemplate 实现数据的添加,修改,删除

实例

向表中新增两条记录,用两种方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数据添加
@Test
public void InsertJdbcTemplate() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
DataSource dataSource = ioc.getBean("dataSource", DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println("dataSource's class = " + dataSource.getConnection());
System.out.println("connection = " + connection);

JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate", JdbcTemplate.class);
String sql1 = "INSERT INTO t_user VALUE (1113, 'zjmd', 23, '3783489@qq.com');";
String sql2 = "INSERT INTO t_user VALUE (?, ?, ?, ?);";

// 添加方式1 通过execute的方式
jdbcTemplate.execute(sql1);
//添加方式二 :通过update方法添加,按照顺序传递参数
int affectedRows = jdbcTemplate.update(sql2, 221, "wh", 34, "234e3434@email.com");
System.out.println("affected rows = " + affectedRows);

connection.close();
}

测试通过,发现可以插入数据

注意的是 excute()是 JDBC 原生方法的简单封装,用于执行完整 SQL 字符串

1
2
3
4
5
6
7
// 注意:需手动转义参数值,避免安全问题!
int id = 111;
String name = "zjm";
int age = 23;
String unsafeSql = String.format("INSERT INTO t_user VALUES (%d, '%s', %d)", id, name, age);
jdbcTemplate.execute(unsafeSql); // 仅用于演示,生产环境禁止!
// 只能做到静态sql语句的插入

修改和删除的方法类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 数据修改
@Test
public void UpdateJdbcTemplate() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate", JdbcTemplate.class);

// 方式1:直接执行静态SQL
String sql1 = "UPDATE t_user SET name='zjm_updated', age=25 WHERE id=1113";
jdbcTemplate.execute(sql1);

// 方式2:使用参数化SQL(推荐)
String sql2 = "UPDATE t_user SET name=?, age=? WHERE id=?";
int affectedRows = jdbcTemplate.update(sql2, "wh_updated", 35, 221);
System.out.println("Updated rows: " + affectedRows);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 数据删除
@Test
public void DeleteJdbcTemplate() throws SQLException {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate", JdbcTemplate.class);

// 方式1:直接执行静态SQL
String sql1 = "DELETE FROM t_user WHERE id=1113";
jdbcTemplate.execute(sql1);

// 方式2:使用参数化SQL(推荐)
String sql2 = "DELETE FROM t_user WHERE id=?";
int affectedRows = jdbcTemplate.update(sql2, 221);
System.out.println("Deleted rows: " + affectedRows);
}

update方法详解

可以看到,JDBCTemplate对数据库的增删改操作都是update方法,执行最终的效果只根据语句有关系

update 方法的主要作用是执行 SQL 语句并返回受影响的行数。它可以执行的 SQL 语句包括 INSERTUPDATEDELETE 语句。

返回值:update 方法的返回值是一个整数,表示 SQL 语句执行后受影响的行数

image-20250427171122032

其中方法的重载形式:

int update(String sql)

  • 参数说明:
    • sql:要执行的 SQL 语句。
  • 使用场景:当 SQL 语句中不包含参数时,可以使用这个方法。例如,删除表中所有数据:
1
2
3
4
5
6
7
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已经正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "DELETE FROM users";
int rows = jdbcTemplate.update(sql);
System.out.println("删除的行数: " + rows);

int update(String sql, @Nullable Object... args)

  • 参数说明
    • sql:要执行的 SQL 语句,其中可以包含占位符 ?
    • args:SQL 语句中占位符对应的参数值,参数的顺序要和占位符的顺序一致。
  • 使用场景:当 SQL 语句中包含参数时,可以使用这个方法。例如,插入一条新记录:
1
2
3
4
5
6
7
8
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已经正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
Object[] args = {"John", 25};
int rows = jdbcTemplate.update(sql, args);
System.out.println("插入的行数: " + rows);

int update(String sql, PreparedStatementSetter pss)

  • 参数说明
    • sql:要执行的 SQL 语句,其中可以包含占位符 ?
    • pss:一个 PreparedStatementSetter 接口的实现类,用于设置 PreparedStatement 对象的参数。
  • 使用场景:当需要更复杂的参数设置逻辑时,可以使用这个方法。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;

import java.sql.PreparedStatement;
import java.sql.SQLException;

// 假设 jdbcTemplate 已经正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "UPDATE users SET age = ? WHERE name = ?";
int age = 26;
String name = "John";
int rows = jdbcTemplate.update(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1, age);
ps.setString(2, name);
}
});
System.out.println("更新的行数: " + rows);

异常处理

update 方法可能会抛出 DataAccessException 及其子类的异常,例如 SQLException 等。在实际使用中,建议捕获这些异常并进行相应的处理。例如:

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已经正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "DELETE FROM users";
try {
int rows = jdbcTemplate.update(sql);
System.out.println("删除的行数: " + rows);
} catch (DataAccessException e) {
System.err.println("数据库操作出错: " + e.getMessage());
}

通过 JDBCTemplate 实现数据的查询

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 数据查询
@Test
public void SelectJdbcTemplate() throws SQLException {
String sql = "SELECT * FROM t_user WHERE id=?";

// 查询返回 Map
Map<String, Object> userMap = jdbcTemplate.queryForMap(sql, 1113);
System.out.println("User as Map: " + userMap);

// 查询返回对象
// 写法1
User user = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
u.setAge(rs.getInt("age"));
u.setEmail(rs.getString("email"));
return u;
}, 1113);
System.out.println("User as Object: " + user);
// 写法2
User user2 = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1113);
System.out.println("User as Object: " + user2);

// 返回集合
// 返回List<User>
List<User> users = jdbcTemplate.query(sql, (rs, rowNum) -> {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
u.setAge(rs.getInt("age"));
u.setEmail(rs.getString("email"));
return u;
}, 20);
System.out.println("Users as Objects: " + users);

// 返回List<Map>
List<Map<String, Object>> userMaps = jdbcTemplate.queryForList(sql, 20);
System.out.println("Users as Maps: " + userMaps);

// 返回单个值,查询有多少记录
String sql2 = "select count(*) from t_user";
Integer count = jdbcTemplate.queryForObject(sql2, Integer.class);
System.out.println("Count of users: " + count);
}

queryForObject方法详解

可以看到,JDBCTemplate对数据库的增删改操作都是queryForObject方法,执行的结果跟这个方法重载的各种类型有关系。

queryForObject()方法中,传入SQL以及SQL参数后,JdbcTemplate会自动创建PreparedStatement,自动执行查询并返回ResultSet,我们提供的RowMapper需要做的事情就是把ResultSet的当前行映射成一个JavaBean并返回。整个过程中,使用ConnectionPreparedStatementResultSet都不需要我们手动管理。

JdbcTemplate 中的 queryForObject 方法主要用于执行 SQL 查询语句,并期望返回单个结果对象(集合).

queryForObject 方法用于执行查询操作,且预期查询结果为单个对象。当你确定查询只会返回一条记录时,就可以使用这个方法将结果映射到指定的对象类型中。

常见重载形式

T queryForObject(String sql, Class requiredType)

  • 参数说明

    • sql:要执行的 SQL 查询语句,此语句一般用于查询单个值,如查询某一列的总和、平均值等。
  • requiredType:期望返回结果的类型,比如 IntegerString 等。

  • 使用场景:当查询结果是单个值时使用。例如,查询 users 表中的用户总数:

1
2
3
4
5
6
7
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "SELECT COUNT(*) FROM users";
Integer userCount = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("用户总数: " + userCount);

T queryForObject(String sql, Object[] args, Class requiredType)

  • 参数说明

    • sql:包含占位符 ? 的 SQL 查询语句。
  • args:SQL 语句中占位符对应的参数值数组,参数顺序需与占位符顺序一致。

    • requiredType:期望返回结果的类型。
  • 使用场景:当查询语句包含参数且结果为单个值时使用。例如,查询名为 John 的用户的年龄:

1
2
3
4
5
6
7
8
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "SELECT age FROM users WHERE name = ?";
Object[] args = {"John"};
Integer age = jdbcTemplate.queryForObject(sql, args, Integer.class);
System.out.println("John 的年龄: " + age);

T queryForObject(String sql, RowMapper rowMapper)

  • 参数说明

    • sql:要执行的 SQL 查询语句,用于查询完整的记录。
  • rowMapperRowMapper 接口的实现类,用于将查询结果集中的每一行数据映射到指定类型的对象。

  • 使用场景:当查询结果是一条完整的记录,需要将其映射到自定义对象时使用。例如,查询 id 为 1 的用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

// 假设 jdbcTemplate 已正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "SELECT * FROM users WHERE id = 1";
User user = jdbcTemplate.queryForObject(sql, new 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"));
return user;
}
});
System.out.println(user);

T queryForObject(String sql, Object[] args, RowMapper rowMapper)

  • 参数说明

    • sql:包含占位符 ? 的 SQL 查询语句。
  • args:SQL 语句中占位符对应的参数值数组。

    • rowMapperRowMapper 接口的实现类,用于将查询结果集中的每一行数据映射到指定类型的对象。
  • 使用场景:当查询语句包含参数,且需要将查询结果映射到自定义对象时使用。例如,查询名为 John 的用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

// 假设 jdbcTemplate 已正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "SELECT * FROM users WHERE name = ?";
Object[] args = {"John"};
User user = jdbcTemplate.queryForObject(sql, args, new 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"));
return user;
}
});
System.out.println(user);

返回值

queryForObject 方法的返回值是一个指定类型的对象。如果查询结果为空,该方法可能会抛出 EmptyResultDataAccessException 异常;如果查询结果有多条记录,也会抛出异常。

异常处理

在使用 queryForObject 方法时,需要注意处理可能抛出的异常,如 EmptyResultDataAccessExceptionIncorrectResultSizeDataAccessException

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

// 假设 jdbcTemplate 已正确初始化
JdbcTemplate jdbcTemplate = ...;
String sql = "SELECT age FROM users WHERE name = ?";
Object[] args = {"NonExistentUser"};
try {
Integer age = jdbcTemplate.queryForObject(sql, args, Integer.class);
System.out.println("年龄: " + age);
} catch (EmptyResultDataAccessException e) {
System.err.println("未找到符合条件的记录: " + e.getMessage());
}

更进阶的使用

数据批量处理

JdbcTemplate 提供 batchUpdate() 方法实现批量操作,适合一次性处理大量数据,提升性能。

参数说明:

  • batchUpdate(String sql, List batchArgs)
    • batchArgs 中每个 Object[] 对应 SQL 中的一组参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void BatchInsertJdbcTemplate() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
JdbcTemplate jdbcTemplate = ioc.getBean("jdbcTemplate", JdbcTemplate.class);

String sql = "INSERT INTO t_user (name, age, email) VALUES (?, ?, ?)";

// 批量数据:参数为 Object 数组的集合
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"Alice", 28, "alice@example.com"});
batchArgs.add(new Object[]{"Bob", 32, "bob@example.com"});
batchArgs.add(new Object[]{"Charlie", 25, "charlie@example.com"});

// 执行批量插入
int[] affectedRows = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println("插入行数统计: " + Arrays.toString(affectedRows));
}

查询到的数据封装到 Bean 对象

通过 RowMapperBeanPropertyRowMapper 将结果集自动映射到 Java 对象。

自定义 RowMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
}

// 使用示例
@Test
public void QueryWithCustomRowMapper() {
String sql = "SELECT * FROM t_user WHERE age > ?";
List<User> users = jdbcTemplate.query(
sql,
new UserRowMapper(),
20 // 参数:age > 20
);
System.out.println("Users: " + users);
}

使用 BeanPropertyRowMapper

Spring 提供的自动映射工具,要求数据库字段名与 Java 对象属性名一致(如 user_nameuserName)。

1
2
3
4
5
6
7
8
9
@Test
public void QueryWithBeanPropertyRowMapper() {
String sql = "SELECT * FROM t_user";
List<User> users = jdbcTemplate.query(
sql,
new BeanPropertyRowMapper<>(User.class)
);
System.out.println("Users: " + users);
}

查询到的数据以Bean对象的形式封装到集合中

直接使用 query() 方法返回 List,适用于查询多条记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void QueryAllUsersAsBeanList() {
String sql = "SELECT * FROM t_user";
List<User> users = jdbcTemplate.query(
sql,
(rs, rowNum) -> {
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;
}
);
System.out.println("All Users: " + users);
}

查询单行单列的具体字段

使用 queryForObject() 直接返回基本类型值(如 Integer, String)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void QuerySingleValue() {
// 查询最大年龄
String sqlMaxAge = "SELECT MAX(age) FROM t_user";
Integer maxAge = jdbcTemplate.queryForObject(sqlMaxAge, Integer.class);
System.out.println("最大年龄: " + maxAge);

// 查询特定用户的邮箱
String sqlEmail = "SELECT email FROM t_user WHERE name = ?";
String email = jdbcTemplate.queryForObject(
sqlEmail,
String.class,
"Alice"
);
System.out.println("Alice 的邮箱: " + email);
}

使用具名参数添加数据

以往我们使用SQL语句时,总是用到占位符 ? ,就像我们上面刚刚用到的SQL语句

这么做有个问题——除非我们知道对应表的结构,不然就无法确定这个占位符? 应该传入什么参数

使用具名参数可以很好的解决这个问题。

通过 NamedParameterJdbcTemplate 支持具名参数(如 :name),提升 SQL 可读性。

首先在之前的 xml 配置文件下新配置一个 NamedParameterJdbcTemplate 对象,如下

1
2
3
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void InsertWithNamedParameters() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
NamedParameterJdbcTemplate namedTemplate = ioc.getBean(
"namedParameterJdbcTemplate",
NamedParameterJdbcTemplate.class
);

String sql = "INSERT INTO t_user (name, age, email) VALUES (:name, :age, :email)";

// 使用 Map 传递参数
Map<String, Object> params = new HashMap<>();
params.put("name", "David");
params.put("age", 29);
params.put("email", "david@example.com");

int affectedRows = namedTemplate.update(sql, params);
System.out.println("插入行数: " + affectedRows);
}

使用 SqlParameterSource

更灵活的传参方式(支持 Bean 对象自动映射):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void InsertWithSqlParameterSource() {
NamedParameterJdbcTemplate namedTemplate = ...; // 获取 Bean

String sql = "INSERT INTO t_user (name, age, email) VALUES (:name, :age, :email)";

// 使用 BeanPropertySqlParameterSource
User newUser = new User();
newUser.setName("Eva");
newUser.setAge(27);
newUser.setEmail("eva@example.com");

SqlParameterSource params = new BeanPropertySqlParameterSource(newUser);
int affectedRows = namedTemplate.update(sql, params);
System.out.println("插入行数: " + affectedRows);
}

总结

我们总结一下JdbcTemplate的用法,那就是:

  • 针对简单查询,优选query()queryForObject(),因为只需提供SQL语句、参数和RowMapper
  • 针对更新操作,优选update(),因为只需提供SQL语句和参数;
  • 任何复杂的操作,最终也可以通过execute(ConnectionCallback)实现,因为拿到Connection就可以做任何JDBC操作。

实际上我们使用最多的仍然是各种查询。如果在设计表结构的时候,能够和JavaBean的属性一一对应,那么直接使用BeanPropertyRowMapper就很方便。如果表结构和JavaBean不一致怎么办?那就需要稍微改写一下查询,使结果集的结构和JavaBean保持一致。

例如,表的列名是office_address,而JavaBean属性是workAddress,就需要指定别名,改写查询如下

1
SELECT id, email, office_address AS workAddress, name FROM users WHERE email = ?