1. 概述
Spring 的 JdbcTemplate
是一个非常实用的数据访问工具,它让开发者可以专注于 SQL 编写和结果处理,而不用操心底层的连接管理、资源释放等琐事。它直接与数据库交互,执行 SQL 并封装结果。
既然它涉及数据库操作,我们通常会考虑使用集成测试来验证数据查询是否正确。但与此同时,单元测试也有其价值——它可以快速验证业务逻辑、方法流程,而无需启动数据库。
本文将介绍几种对 JdbcTemplate
相关代码进行单元测试的常用方式,帮助你在不同场景下做出合适选择。
2. JdbcTemplate 与查询执行
我们先来看一个典型的 DAO 类,它使用了 JdbcTemplate
:
public class EmployeeDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public int getCountOfEmployees() {
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EMPLOYEE", Integer.class);
}
}
这个类通过依赖注入接收一个 DataSource
,并在 setDataSource
方法中初始化 JdbcTemplate
。getCountOfEmployees()
方法则使用 JdbcTemplate
执行一条简单的 COUNT 查询。
对于这类方法的测试,主要有两种思路:
✅ 使用内存数据库(如 H2)作为数据源
真实执行 SQL,适合验证 SQL 逻辑和数据映射是否正确。
✅ 直接 Mock JdbcTemplate
跳过数据库交互,只验证方法是否正确调用了 JdbcTemplate
的 API,适合快速测试逻辑分支。
下面分别展开。
3. 使用 H2 数据库进行单元测试
我们可以构建一个指向 H2 内存数据库的 DataSource
,并将其注入到 DAO 中,从而在真实(但轻量)的数据库环境中运行测试。
@Test
public void whenInjectInMemoryDataSource_thenReturnCorrectEmployeeCount() {
DataSource dataSource = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
.addScript("classpath:jdbc/schema.sql")
.addScript("classpath:jdbc/test-data.sql")
.build();
EmployeeDAO employeeDAO = new EmployeeDAO();
employeeDAO.setDataSource(dataSource);
assertEquals(4, employeeDAO.getCountOfEmployees());
}
关键步骤说明:
- 使用
EmbeddedDatabaseBuilder
快速搭建 H2 内存库 - 通过
addScript
加载建表和测试数据脚本
对应的 schema.sql
:
CREATE TABLE EMPLOYEE
(
ID int NOT NULL PRIMARY KEY,
FIRST_NAME varchar(255),
LAST_NAME varchar(255),
ADDRESS varchar(255)
);
test-data.sql
:
INSERT INTO EMPLOYEE VALUES (1, 'James', 'Gosling', 'Canada');
INSERT INTO EMPLOYEE VALUES (2, 'Donald', 'Knuth', 'USA');
INSERT INTO EMPLOYEE VALUES (3, 'Linus', 'Torvalds', 'Finland');
INSERT INTO EMPLOYEE VALUES (4, 'Dennis', 'Ritchie', 'USA');
⚠️ 踩坑提示:
如果 SQL 脚本路径不对或表名大小写不匹配(H2 默认转大写),可能导致表不存在或查询为空。建议统一使用大写表名,或在连接 URL 中添加 ;MODE=MySQL
等模式兼容配置。
这种方式适合验证复杂 SQL、JOIN、事务等真实场景,但需要维护脚本,略显繁琐。
4. 使用 Mock 对象进行单元测试
如果我们只关心方法是否正确调用了 JdbcTemplate
,而不想依赖数据库,就可以使用 Mock 框架(如 Mockito)来模拟 JdbcTemplate
的行为。
public class EmployeeDAOUnitTest {
@Mock
JdbcTemplate jdbcTemplate;
@Test
public void whenMockJdbcTemplate_thenReturnCorrectEmployeeCount() {
EmployeeDAO employeeDAO = new EmployeeDAO();
ReflectionTestUtils.setField(employeeDAO, "jdbcTemplate", jdbcTemplate);
Mockito.when(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM EMPLOYEE", Integer.class))
.thenReturn(4);
assertEquals(4, employeeDAO.getCountOfEmployees());
}
}
核心要点:
- 使用
@Mock
创建JdbcTemplate
的 Mock 实例 - 通过
ReflectionTestUtils.setField()
反射注入私有字段(Spring Test 提供的工具类) - 使用
Mockito.when(...).thenReturn(...)
预设方法返回值
避免字符串硬匹配
上面的测试对 SQL 字符串做了完全匹配,但在实际项目中,SQL 可能是拼接的或动态生成的,硬编码字符串容易导致测试脆弱。
我们可以改用 Mockito 的参数匹配器来规避这个问题:
Mockito.when(jdbcTemplate.queryForObject(Mockito.anyString(), Mockito.eq(Integer.class)))
.thenReturn(3);
assertEquals(3, employeeDAO.getCountOfEmployees());
✅ 推荐做法:
anyString()
匹配任意 SQL 字符串eq(Integer.class)
确保返回类型正确
这样即使 SQL 稍有变化,只要逻辑正确,测试仍能通过。
⚠️ 注意:
过度使用 anyString()
可能掩盖 SQL 错误,建议在逻辑测试中使用,在SQL 验证测试中仍应使用具体字符串。
5. Spring Boot 中的 @JdbcTest
如果你在使用 Spring Boot,那就有更简单的办法了 —— 使用 @JdbcTest
注解。
它会自动配置一个内存数据库(默认 H2)、初始化 JdbcTemplate
Bean,并只加载数据访问相关的组件,非常适合 DAO 层测试。
@JdbcTest
@Sql({"schema.sql", "test-data.sql"})
class EmployeeDAOIntegrationTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
void whenInjectInMemoryDataSource_thenReturnCorrectEmployeeCount() {
EmployeeDAO employeeDAO = new EmployeeDAO();
employeeDAO.setJdbcTemplate(jdbcTemplate);
assertEquals(4, employeeDAO.getCountOfEmployees());
}
}
优势:
- 自动装配
JdbcTemplate
@Sql
注解可指定初始化脚本,简洁明了- 测试启动快,隔离性好
✅ 建议:
对于 Spring Boot 项目,优先考虑 @JdbcTest + @Sql
组合,简单粗暴又可靠。
6. 总结
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
H2 内存数据库 | 验证 SQL 正确性 | 接近真实环境 | 需维护脚本 |
Mock JdbcTemplate | 验证方法调用逻辑 | 快速、无依赖 | 不验证 SQL |
@JdbcTest (Spring Boot) |
Spring Boot 项目 DAO 测试 | 自动化程度高 | 仅限 Spring Boot |
选择哪种方式,取决于你的测试目标:
- 要验证 SQL 是否正确?用 H2 或
@JdbcTest
- 要验证 逻辑分支或异常处理?用 Mock
- 是 Spring Boot 项目?优先
@JdbcTest
示例代码已整理至 GitHub:https://github.com/baomidou/tutorials/tree/master/spring-jdbc-testing