Spring JDBC源码解析:模板方法模式的优雅实践
文章目录
- 1. 引言:从原生JDBC的困境说起
- 2. 模板方法模式在JdbcTemplate中的体现
- 2.1 设计模式解析
- 2.2 实际源码中的模板方法
- 3. 回调接口:变化部分的抽象
- 3.1 核心回调接口
- 3.2 回调接口的使用示例
- 4. 异常体系:从SQLException到DataAccessException
- 4.1 异常转换的必要性
- 4.2 Spring的异常转换机制
- 4.3 Spring JDBC中的异常转换器
- SQLStateSQLExceptionTranslator
- SQLErrorCodeSQLExceptionTranslator
- CustomSQLErrorCodesTranslation
- 设计思想
- 4.4 异常层次结构
- 5. 资源管理:连接获取与释放的秘密
- 5.1 连接获取的双重策略
- 5.2 资源清理的智能策略
- 6. 实际工作流程分析
- 7. 设计优势与启示
- 7.1 模板方法模式带来的好处
- 7.2 资源管理的智慧
- 7.3 异常处理的优雅
- 8. 总结
在Spring的JDBC模块中,
JdbcTemplate以其简洁的API和强大的功能成为了数据访问层的核心。今天,我们将深入源码,探寻模板方法模式在其中扮演的关键角色,并解析其异常体系和资源管理机制。
关于Spring数据访问JDBC与事务架构总览可参阅:>> Spring数据访问基石:JDBC与事务架构总览<<
1. 引言:从原生JDBC的困境说起
在原生JDBC编程中,我们不得不面对大量重复且容易出错的样板代码:
// 原生JDBC的典型代码 - 繁琐且容易出错
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {conn = dataSource.getConnection();stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");stmt.setLong(1, userId);rs = stmt.executeQuery();// 处理结果集...
} catch (SQLException e) {// 异常处理...
} finally {// 资源清理 - 容易遗漏且顺序重要try { if (rs != null) rs.close(); } catch (SQLException e) { /* 忽略 */ }try { if (stmt != null) stmt.close(); } catch (SQLException e) { /* 忽略 */ }try { if (conn != null) conn.close(); } catch (SQLException e) { /* 忽略 */ }
}
Spring的JdbcTemplate通过模板方法模式,将上述代码简化为:
// 使用JdbcTemplate的优雅代码
User user = jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", new Object[]{userId},new BeanPropertyRowMapper<>(User.class)
);
2. 模板方法模式在JdbcTemplate中的体现
2.1 设计模式解析
模板方法模式定义了一个操作的算法骨架,而将一些步骤延迟到子类中。JdbcTemplate完美地运用了这一模式:
源码位置:org.springframework.jdbc.core.JdbcTemplate

2.2 实际源码中的模板方法
在Spring 5.x中JdbcTemplate.query方法的实际实现:

3. 回调接口:变化部分的抽象
Spring JDBC通过一系列回调接口,将变化的部分抽象出来,让使用者可以专注于业务逻辑:
3.1 核心回调接口
源码位置:org.springframework.jdbc.core.ConnectionCallback

源码位置:org.springframework.jdbc.core.PreparedStatementCallback

源码位置:org.springframework.jdbc.core.RowMapper

源码位置:org.springframework.jdbc.core.ResultSetExtractor

3.2 回调接口的使用示例
// 使用ConnectionCallback自定义连接操作
jdbcTemplate.execute(new ConnectionCallback<Object>() {@Overridepublic Object doInConnection(Connection conn) throws SQLException {try (CallableStatement cs = conn.prepareCall("{call my_stored_proc(?)}")) {cs.setString(1, "parameter");cs.execute();return null;}}
});// 使用 PreparedStatementCallback 执行更新操作
Integer result = jdbcTemplate.execute("UPDATE users SET name = ? WHERE id = ?", new PreparedStatementCallback<Integer>() {@Overridepublic Integer doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {ps.setString(1, "John");ps.setInt(2, 1);return ps.executeUpdate();}});// 使用RowMapper进行结果集映射
List<User> users = jdbcTemplate.query("SELECT id, name, email FROM users", new RowMapper<User>() {@Overridepublic User mapRow(ResultSet rs, int rowNum) throws SQLException {User user = new User();user.setId(rs.getLong("id"));user.setName(rs.getString("name")); user.setEmail(rs.getString("email"));return user;}}
);// 使用ResultSetExtractor将整个结果集转换为一个统计对象
UserStatistics stats = jdbcTemplate.query("SELECT COUNT(*) as total, AVG(age) as average_age FROM users",new ResultSetExtractor<UserStatistics>() {@Overridepublic UserStatistics extractData(ResultSet rs) throws SQLException, DataAccessException {if (rs.next()) {UserStatistics stats = new UserStatistics();stats.setTotalUsers(rs.getInt("total"));stats.setAverageAge(rs.getDouble("average_age"));return stats;}return null;}}
);
4. 异常体系:从SQLException到DataAccessException
4.1 异常转换的必要性
JDBC的SQLException存在几个问题:
- 检查异常:强制捕获,导致代码冗余
- 信息混杂:包含连接、SQL、约束等多种错误类型
- 厂商差异:不同数据库厂商的错误码和状态码不同
4.2 Spring的异常转换机制


4.3 Spring JDBC中的异常转换器
源码接口:org.springframework.jdbc.support.SQLExceptionTranslator

由源码可知SQLExceptionTranslator 接口有多个实现类,每个都有不同的作用和特点:
主要实现类及作用:
SQLStateSQLExceptionTranslator
- 源码位置:
org.springframework.jdbc.support.SQLStateSQLExceptionTranslator - 作用: 基于 SQL 标准的 SQLState 代码进行异常转换
- 特点: 提供数据库无关的通用异常翻译机制,是最基础的实现
- 使用场景: 当没有特定数据库配置时的默认 fallback 机制
SQLErrorCodeSQLExceptionTranslator
- 源码位置:
org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator - 作用: 基于数据库厂商特定的错误代码进行异常转换
- 特点: 使用 sql-error-codes.xml 配置文件,针对不同数据库提供精确的异常映射
- 使用场景: 生产环境中推荐使用的实现,提供最佳的异常转换精度
CustomSQLErrorCodesTranslation
- 源码位置:
org.springframework.jdbc.support.CustomSQLErrorCodesTranslation - 作用: 支持用户自定义错误代码映射
- 特点: 提供扩展机制,允许用户添加特定的错误代码转换规则
设计思想
- 在
AbstractFallbackSQLExceptionTranslator中,默认设置fallbackTranslator为SQLStateSQLExceptionTranslator实例 SQLErrorCodeSQLExceptionTranslator通常被用作主要的异常翻译器,但在无法识别特定异常时会回退到SQLStateSQLExceptionTranslator- 当所有翻译都失败时,会抛出
UncategorizedSQLException
这种设计提供了三层异常翻译机制:
- 数据库特定的错误代码翻译(最高精度)
- 标准 SQLState 翻译(通用 fallback)
- 未分类异常(最终兜底)
默认实现
Spring JDBC 中的默认SQL异常转换器实现是 SQLStateSQLExceptionTranslator,基于源码详细看下具体实现:

4.4 异常层次结构
Spring构建了清晰的异常层次结构:
DataAccessException (RuntimeException)
├── NonTransientDataAccessException
│ ├── DataIntegrityViolationException
│ ├── InvalidDataAccessResourceUsageException
│ ├── DataAccessResourceFailureException
│ └── PermissionDeniedDataAccessException
│ └── ...
├── TransientDataAccessException
│ ├── ConcurrencyFailureException
│ │ ├── OptimisticLockingFailureException
│ │ └── PessimisticLockingFailureException
│ │ ├── CannotAcquireLockException
│ │ ├── DeadlockLoserDataAccessException
│ │ └── CannotSerializeTransactionException
│ │ └── ...
│ ├── QueryTimeoutException
│ └── TransientDataAccessResourceException
│ └── ...
└── RecoverableDataAccessException
关键类说明
NonTransientDataAccessException: 表示非瞬时异常,通常不会因为重试而成功TransientDataAccessException: 表示瞬时异常,有可能通过重试解决RecoverableDataAccessException: 表示可恢复的异常,通常可以通过修正应用级别错误来解决
5. 资源管理:连接获取与释放的秘密
5.1 连接获取的双重策略
JdbcTemplate并不直接调用DataSource.getConnection(),而是通过DataSourceUtils:



使用DataSourceUtils的优势:
- 事务一致性:确保同一事务中的所有操作使用同一个数据库连接
- 连接复用:避免在事务中重复创建连接,提高性能
- 资源管理:自动处理连接的绑定、解绑和释放
- 异常统一:提供一致的异常处理机制
- 透明集成:与 Spring 的声明式事务无缝集成
5.2 资源清理的智能策略


6. 实际工作流程分析
下面通过一个完整的查询示例,分析JdbcTemplate的工作流程:
// 用户调用的高层API
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {return query(sql, new RowMapperResultSetExtractor<>(rowMapper));
}// 中间层实现
public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {return query(sql, new ResultSetExtractorCallback<>(rse));
}// 底层模板方法
public <T> T query(String sql, final PreparedStatementCallback<T> action) throws DataAccessException {return execute(new SimplePreparedStatementCreator(sql), action);
}// 最终的模板方法执行
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) {// 完整的固定流程:// 1. 获取连接(支持事务)// 2. 创建PreparedStatement// 3. 设置参数(如果有)// 4. 执行回调(用户自定义逻辑)// 5. 处理警告// 6. 转换异常// 7. 释放资源
}
7. 设计优势与启示
7.1 模板方法模式带来的好处
- 消除重复代码:将JDBC的样板代码抽取到模板中
- 关注点分离:固定流程与变化逻辑清晰分离
- 一致性保证:所有数据库操作都遵循相同的资源管理规则
- 易于维护:资源管理和异常处理逻辑集中在一处
7.2 资源管理的智慧
- 事务感知:智能识别事务上下文,避免过早释放连接
- 引用计数:支持同一连接的多次获取和释放
- 防御性编程:确保资源在任何情况下都能被正确清理
7.3 异常处理的优雅
- 统一转换:将数据库特定的异常转换为平台无关的异常
- 丰富语义:通过异常层次结构提供准确的错误信息
- 非侵入性:使用者无需关心底层的异常处理
8. 总结
通过深入分析JdbcTemplate的源码,我们看到了模板方法模式在解决复杂流程问题时的强大威力。Spring通过这种设计:
- 将繁琐变得简单:用户只需关注核心的SQL和业务逻辑
- 将复杂变得可控:资源管理和异常处理被系统化地处理
- 将特定变得通用:不同数据库的差异被统一抽象
这种"固定流程+可变逻辑"的设计思想,不仅体现在JDBC模块中,也是Spring框架诸多模块的设计哲学。
下一篇预告:《Spring事务机制揭秘:AOP代理的魔法背后》 - 我们将深入分析Spring如何通过AOP和动态代理实现声明式事务,揭开@Transactional注解背后的神秘面纱。
