当前位置: 首页 > news >正文

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
  • 作用: 支持用户自定义错误代码映射
  • 特点: 提供扩展机制,允许用户添加特定的错误代码转换规则
设计思想
  1. AbstractFallbackSQLExceptionTranslator 中,默认设置 fallbackTranslatorSQLStateSQLExceptionTranslator 实例
  2. SQLErrorCodeSQLExceptionTranslator 通常被用作主要的异常翻译器,但在无法识别特定异常时会回退到 SQLStateSQLExceptionTranslator
  3. 当所有翻译都失败时,会抛出 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

关键类说明

  1. NonTransientDataAccessException: 表示非瞬时异常,通常不会因为重试而成功
  2. TransientDataAccessException: 表示瞬时异常,有可能通过重试解决
  3. RecoverableDataAccessException: 表示可恢复的异常,通常可以通过修正应用级别错误来解决

5. 资源管理:连接获取与释放的秘密

5.1 连接获取的双重策略

JdbcTemplate并不直接调用DataSource.getConnection(),而是通过DataSourceUtils
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用DataSourceUtils的优势

  1. 事务一致性:确保同一事务中的所有操作使用同一个数据库连接
  2. 连接复用:避免在事务中重复创建连接,提高性能
  3. 资源管理:自动处理连接的绑定、解绑和释放
  4. 异常统一:提供一致的异常处理机制
  5. 透明集成:与 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注解背后的神秘面纱。

http://www.dtcms.com/a/585159.html

相关文章:

  • 19-Node.js 操作 Redis 实战指南:ioredis 客户端全解析与异步场景落地
  • linux服务-iptables 原理及示例详解
  • Firebase 架构原理与实战入门:从零搭建你的第一个云端应用
  • 精品在线试题库系统|基于SpringBoot和Vue的精品在线试题库系统(源码+数据库+文档)
  • AI时代职场反脆弱性:杠铃策略平衡稳定工作与高风险创新
  • 网站搭建的步骤wordpress 添加评论
  • SLAM中的非线性优-3D图优化之轴角在Opencv-PNP中的应用(一)
  • Rust 练习册 :Poker与扑克牌游戏
  • 【python】基础案例分析
  • LeetCode(python)——15.三数之和
  • Java基础——集合进阶用到的数据结构知识点1
  • 无线交换机(AC)核心技术详解:构建集中式Wi-Fi网络的基石
  • DNS的正向、反向解析的服务配置知识点及实验
  • 庖丁解牛:深度剖析 Ascend C 算子开发流程与核心概念
  • 《Learn Python Programming(4th)》读后感
  • 网站开发毕业生报告网页设计与制作项目教程陈义文
  • SSM基于JAVA的物流管理系统ztwfg(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 如何在 Ubuntu 上安装 PostgreSQL
  • openssl-1_0_0-1.0.2p-3.49.1.x86_64.rpm 怎么安装?CentOS/RHEL 手动安装RPM包详细步骤
  • C++ 面试高频题 链表 模拟 力扣 143. 重排链表 题解 每日一题
  • 快速定位bug,编写测试用例
  • 力扣第 474 场周赛
  • Node与Npm国内最新镜像配置(淘宝镜像/清华大学镜像)
  • 超越时空网上书城网站建设方案网站人员队伍建设落后
  • 海外云手机是指什么
  • react native 手搓数字键盘
  • 算法复杂度解析:时间与空间的衡量
  • 开源鸿蒙SIG-Qt技术沙龙成都站成功举办,产品方案展示
  • 2025年渗透测试面试题总结-235(题目+回答)
  • C语言进阶:深入理解函数