Spring JDBC 源码初探:异常处理体系

一、Spring JDBC 异常体系简介

当我们使用 Spring JDBC 进行数据访问时,大多数人关注的是 JdbcTemplate 如何简化数据库操作,却很少有人去深入理解异常体系。事实上,异常不仅仅是错误提示,它是系统健壮性、可维护性的重要一环。JDBC 原生的 SQLException 是受检异常,迫使你在每一层写 try-catch,而 Spring 将其包装成 DataAccessException(运行时异常,无需代码处理),让代码更简洁,异常处理更集中。

二、异常处理体系源码分析

关键包与类一览

  • 统一异常层(与数据访问无关的通用抽象)
    org.springframework.dao.*
    核心:DataAccessException(抽象,继承自 RuntimeException
  • JDBC 侧实现与转换器
    org.springframework.jdbc.support.*
    核心:
    • SQLExceptionTranslator(接口)
    • SQLErrorCodeSQLExceptionTranslator(按厂商 errorCode 转换)
    • SQLStateSQLExceptionTranslator(按 SQLState 转换)
    • SQLExceptionSubclassTranslator(按 JDBC 异常子类转换)

DataAccessException

所有数据访问异常的统一父类,它的作用是将不同数据库、不同持久化技术抛出的异常(如 JDBC、Hibernate、JPA 等)进行统一封装和抽象。DataAccessException 源码在 org.springframework.dao 包中

package org.springframework.dao;import org.springframework.core.NestedRuntimeException;public abstract class DataAccessException extends NestedRuntimeException {// 构造方法,传入异常消息public DataAccessException(String msg) {super(msg);}// 构造方法,传入异常消息和底层异常public DataAccessException(String msg, Throwable cause) {super(msg, cause);}
}

常见子类及应用
Spring 根据异常分类进一步细化,如:

  • DataIntegrityViolationException:违反数据完整性约束(唯一键、外键等)。
  • DataAccessResourceFailureException:数据库资源访问失败(连接失败等)。
  • DuplicateKeyException:唯一键冲突。
  • CannotAcquireLockException:锁无法获取。
  • OptimisticLockingFailureException:乐观锁失败。
  • PermissionDeniedDataAccessException:权限不足。
    在这里插入图片描述

SQLExceptionTranslator

SQLExceptionTranslator 是 Spring JDBC 异常转换机制的核心接口,它的作用是将底层 SQLException 转换成统一的异常(即 DataAccessException 及其子类)
源码位置:org.springframework.jdbc.support.SQLExceptionTranslator,接口定义如下

package org.springframework.jdbc.support;import java.sql.SQLException;import org.springframework.dao.DataAccessException;
import org.springframework.lang.Nullable;@FunctionalInterface
public interface SQLExceptionTranslator {// 将 SQLException 转换成 DataAccessException@NullableDataAccessException translate(String task, @Nullable String sql, SQLException ex);
}

要点:

  • 单一方法 translate:接收参数:任务描述、SQL 语句、原始 SQLException。
  • 返回值:DataAccessException(或其子类)。

主要实现类:

  • SQLErrorCodeSQLExceptionTranslator基于数据库错误码映射
  • SQLStateSQLExceptionTranslator 基于 SQLState 分类
  • SQLExceptionSubclassTranslator 基于SQLException 子类类型

SQLErrorCodeSQLExceptionTranslator

SQLErrorCodeSQLExceptionTranslator作用是将数据库抛出的 SQLException 基于错误码(error code)映射到 Spring 统一的异常体系 DataAccessException
核心源码方法

@Override
@Nullable
protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) {SQLException sqlEx = ex;// 如果异常是 BatchUpdateException,并且内部还有嵌套异常(getNextException()),则取最内层的异常。if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {SQLException nestedSqlEx = sqlEx.getNextException();if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {sqlEx = nestedSqlEx;}}// 先走开发者自定义逻辑,如果子类重写了 customTranslate() 方法,可以自定义转换逻辑。DataAccessException dae = customTranslate(task, sql, sqlEx);if (dae != null) {return dae;}// 获取数据库厂商错误码SQLErrorCodes sqlErrorCodes = getSqlErrorCodes();if (sqlErrorCodes != null) {// 如果配置了 SQLErrorCodes,并且定义了 customSqlExceptionTranslator,优先调用它。SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator();if (customTranslator != null) {DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);if (customDex != null) {return customDex;}}}//  根据错误码映射if (sqlErrorCodes != null) {String errorCode;if (sqlErrorCodes.isUseSqlStateForTranslation()) {errorCode = sqlEx.getSQLState();}else {// 遍历 cause,找到 errorCode 非 0 的 SQLExceptionSQLException current = sqlEx;while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) {current = (SQLException) current.getCause();}errorCode = Integer.toString(current.getErrorCode());}if (errorCode != null) {// 如果在 SQLErrorCodes 中配置了 customTranslations(用户自定义映射),优先使用它CustomSQLErrorCodesTranslation[] customTranslations = sqlErrorCodes.getCustomTranslations();if (customTranslations != null) {for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&customTranslation.getExceptionClass() != null) {DataAccessException customException = createCustomException(task, sql, sqlEx, customTranslation.getExceptionClass());if (customException != null) {logTranslation(task, sql, sqlEx, true);return customException;}}}}// 按 Spring 预定义错误码分类翻译if (Arrays.binarySearch(sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new InvalidResultSetAccessException(task, (sql != null ? sql : ""), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);}else if (Arrays.binarySearch(sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {logTranslation(task, sql, sqlEx, false);return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);}}}// 如果没有匹配,返回 null,由 fallback 翻译器继续if (logger.isDebugEnabled()) {String codes;if (sqlErrorCodes != null && sqlErrorCodes.isUseSqlStateForTranslation()) {codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();}else {codes = "Error code '" + sqlEx.getErrorCode() + "'";}logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator");}return null;
}

错误码配置来源

SQLErrorCodeSQLExceptionTranslator 使用 SQLErrorCodes 对象,该对象包含各种数据库的错误码映射。

配置文件:
org/springframework/jdbc/support/sql-error-codes.xml(Spring 内置),如MySQL部分如下:

<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes"><property name="databaseProductNames"><list><value>MySQL</value><value>MariaDB</value></list></property><property name="badSqlGrammarCodes"><value>1054,1064,1146</value></property><property name="duplicateKeyCodes"><value>1062</value></property><property name="dataIntegrityViolationCodes"><value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557</value></property><property name="dataAccessResourceFailureCodes"><value>1</value></property><property name="cannotAcquireLockCodes"><value>1205,3572</value></property><property name="deadlockLoserCodes"><value>1213</value></property>
</bean>

Spring 会根据 DataSource 的元数据(DatabaseMetaData.getDatabaseProductName()) 自动选择对应数据库的错误码配置。

SQLStateSQLExceptionTranslator

SQLStateSQLExceptionTranslator会根据 SQLException 的 SQLState(标准 SQL 错误码)来转换为 Spring的DataAccessException

SQLState

SQLState 是 JDBC 提供的标准错误码,用于标识数据库操作的错误或状态,一般由5 位字符串,前两位:表示错误类别(Class Code),后两/三位:表示具体错误子类(Subclass Code)

核心源码

@Override
@Nullable
protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) {// 提取 SQLException 的 SQLState(标准5位错误码),取前两位作为“异常类别”String sqlState = getSqlState(ex);if (sqlState != null && sqlState.length() >= 2) {String classCode = sqlState.substring(0, 2);if (logger.isDebugEnabled()) {logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'");}// 根据 SQLState 类匹配 Spring 异常if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) {return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);}else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);}else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) {return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);}else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) {return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);}else if (CONCURRENCY_FAILURE_CODES.contains(classCode)) {return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);}}// 超时处理,检查异常类名是否包含 “Timeout”,如果是则返回 QueryTimeoutExceptionif (ex.getClass().getName().contains("Timeout")) {return new QueryTimeoutException(buildMessage(task, sql, ex), ex);}// 如果没有匹配到任何类别,最终可能被封装为 UncategorizedSQLExceptionreturn null;
}

SQLExceptionSubclassTranslator

主要作用是将标准的 java.sql.SQLException 及其子类异常转换为 Spring 自己定义的.DataAccessException异常体系。

核心源码

@Override
@Nullable
protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) {if (ex instanceof SQLTransientException) {// 处理“瞬时异常”(Transient):这类异常可能在稍后重试时成功。// 例如:数据库死锁、查询超时等。// 根据更具体的子类进行精细翻译if (ex instanceof SQLTransientConnectionException) {//连接相关的瞬时异常,例如连接池暂时无法获取连接return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);}else if (ex instanceof SQLTransactionRollbackException) { // 事务回滚异常,例如死锁return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);}else if (ex instanceof SQLTimeoutException) { // 查询执行超时return new QueryTimeoutException(buildMessage(task, sql, ex), ex);}}// 处理“非瞬时异常”(Non-Transient):这类异常通常不是重试能解决的,是更根本性的报错。else if (ex instanceof SQLNonTransientException) {if (ex instanceof SQLNonTransientConnectionException) {//  连接相关的非瞬时异常,例如无法建立数据库连接、用户名密码错误return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);}//数据问题,例如数据类型错误、数据超出长度else if (ex instanceof SQLDataException) {return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);}// 完整性约束违反else if (ex instanceof SQLIntegrityConstraintViolationException) {return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);}// 授权失败,例如权限不足else if (ex instanceof SQLInvalidAuthorizationSpecException) {return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex);}else if (ex instanceof SQLSyntaxErrorException) {// 无效的SQL语法return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);}else if (ex instanceof SQLFeatureNotSupportedException) {return new InvalidDataAccessApiUsageException(buildMessage(task, sql, ex), ex);}}// 处理“可恢复异常”:这类异常介于瞬态和非瞬态之间,应用程序可能能够从中恢复。// 例如:连接意外中断后可能重新连接成功。else if (ex instanceof SQLRecoverableException) {return new RecoverableDataAccessException(buildMessage(task, sql, ex), ex);}// 如果传入的SQLException不属于任何已知的标准子类,则返回null。return null;
}

逻辑总结

基于 instanceof 对 SQLException 进行类型检查,将其分为三大类:

  • SQLTransientException:瞬时异常。通常意味着操作可以稍后重试并可能成功(如死锁、超时)。
  • SQLNonTransientException:非瞬时异常。意味着存在根本性问题,重试无法解决(如语法错误、违反约束)。
  • SQLRecoverableException:可恢复异常。一种中间状态,应用程序或许能恢复(如连接中断后重连)。

调用顺序

  1. SQLErrorCodeSQLExceptionTranslator默认优先使用的转换器,它依赖于 sql-error-codes.xml 配置文件,将特定数据库的错误代码映射到最具体DataAccessException 子类
  2. SQLExceptionSubclassTranslator,按 JDBC 标准子类
  3. SQLStateSQLExceptionTranslator,按 SQLState 前两位

在每个Translator的构造方法中代码中可以看到,通过 setFallbackTranslator 把链路串起来:

SQLErrorCodeSQLExceptionTranslator

public SQLErrorCodeSQLExceptionTranslator() {setFallbackTranslator(new SQLExceptionSubclassTranslator());
}

SQLExceptionSubclassTranslator

public SQLExceptionSubclassTranslator() {setFallbackTranslator(new SQLStateSQLExceptionTranslator());
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97327.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97327.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何提高微型导轨的生产效率?

在精密机械制造领域&#xff0c;每一个细微的元件都可能成为决定产品性能和品质的关键因素。而微型导轨正是体型小、高精度优势&#xff0c;在精密制造领域得到广泛应用&#xff0c;它高效支撑着现代工业的生产方式和效率。那么&#xff0c;如何提高微型导轨的生产效率呢&#…

轻量xlsx读取库xlsx_drone的编译与测试

这个库是在看其他网页时&#xff0c;作为和功能丰富的xlsxio库的对比来的&#xff0c;按照xlsx_drone github页面介绍&#xff0c; 特征 不使用任何外部应用程序来解析它们。注重速度而不是功能。简单的接口。UTF-8 支持。 安装 直接将 src 和 ext 文件夹复制并粘贴到项目根文…

Linux/UNIX系统编程手册笔记:文件I/O、进程和内存分配

文件 I/O 深度解析&#xff1a;掌握通用 I/O 模型的核心逻辑 在 Linux 系统编程中&#xff0c;文件 I/O 是程序与外部设备&#xff08;文件、设备等 &#xff09;交互的基础。从打开文件到读写数据&#xff0c;再到关闭资源&#xff0c;一系列系统调用构成了通用 I/O 模型的核心…

C++转置正方形矩阵

C转置正方形矩阵&#xff0c;就是正方形矩阵的a[i][j]a[j][i]。输入31 2 34 5 6 7 8 9输出1 4 72 5 83 6 9#include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;int arr[n5][n5];for(int i0;i<n;i){for(int j0;j<n;j){cin>>arr[i][j]…

Ztero文献管理工具插件设置——亲测有效

一、Zotero简介与安装 Zotero是一款开源文献管理软件&#xff0c;能够帮助我们方便地收集、整理、引用和导出文献。它作为一个"在你的网页浏览器中工作的个人研究助手"&#xff0c;可以捕获网页内容并自动添加引用信息。 安装步骤&#xff1a; 访问Zotero官网&…

【gflags】安装与使用

gflags1. 介绍2. 安装3. 使用3.1 头文件3.2 定义参数3.3 访问参数3.4 不同文件访问参数3.5 初始化所有参数3.6 运行参数设置3.7 配置文件的使用3.8 特殊参数标识1. 介绍 gflags 是 Google 开发的一个开源库&#xff0c;用于 C 应用程序中命令行参数的声明、定义和解析。gflags…

基于MATLAB的三维TDOA定位算法仿真实现

一、算法原理与仿真框架 三维TDOA&#xff08;Time Difference of Arrival&#xff09;定位通过测量信号到达多个基站的时间差&#xff0c;结合几何关系反演目标位置。其核心步骤包括&#xff1a;几何建模&#xff1a;建立目标与基站间的距离差方程&#xff0c;形如下式&#x…

Linux-搭建DNS服务器

Linux-搭建DNS服务器1. 安装软件bind2.修改配置文件3. 在其他机器上测试DNS服务器4. 配置本地域名解析5. 优化后的zone1. 安装软件bind bind是历史非常悠久&#xff0c;而且性能非常好的dns域名系统的软件 [rootdns-server ~]# yum install bind bind-utils -y 启动named服务 …

从全栈开发视角看Java与前端技术融合实践

从全栈开发视角看Java与前端技术融合实践 面试场景记录&#xff1a;一次真实的面试对话 面试官&#xff1a;你好&#xff0c;很高兴见到你。我是这次面试的负责人&#xff0c;可以简单介绍一下你自己吗&#xff1f; 应聘者&#xff1a;您好&#xff0c;我叫李明&#xff0c;今年…

第二阶段WinForm-11:自定义控件

1_继承链 &#xff08;1&#xff09;Form1的继承链&#xff1a;Form1>Form>ContainerControl>ScrollableControl>Control &#xff08;2&#xff09;Button的继承链&#xff1a;Button>ButtonBase>Control>Component 2_自定义控件 &#xff08;1&…

【2025 完美解决】Failed connect to github.com:443; Connection timed out

文章目录前言1. 生成并上传 SSH Key2. 写 SSH 配置&#xff0c;强制走 ssh.github.com:4433. 连通性自检&#xff08;看是否能握手成功&#xff09;4. 克隆5. 验证前言 今天和往常一样&#xff0c;写完代码&#xff0c;准备 push 到 github 仓库中&#xff0c;结果发现一直卡在…

C++基础(③反转字符串(字符串 + 双指针))

题目描述&#xff1a;编写一个函数&#xff0c;将输入的字符串反转过来&#xff08;要求原地修改字符串&#xff0c;不使用额外空间&#xff09;。 示例&#xff1a;输入 s ["h","e","l","l","o"] → 输出 ["o",…

vue的动态组件keep-alive实现组件缓存和状态保留

在 Vue.js 中&#xff0c;动态组件结合 keep-alive 是实现组件缓存和状态保留的重要技术方案。以下是详细解析&#xff1a;一、动态组件基础 通过 <component :is> 实现组件动态切换&#xff1a; <component :is"currentComponent"></component>cu…

安装Docker Desktop报错WSL needs updating

&#xff08;1&#xff09;首先观察下面是否勾选&#xff08;2&#xff09;说明已经启动了&#xff0c;但是需要更新&#xff0c;cmd运行下面代码&#xff0c;记得需要开一下代理&#xff0c;可能会有点慢上面就算好了&#xff08;3&#xff09;点击restart这样就代表成功了

♻️旧衣回收小程序|线上模式新升级

还在用老旧的传统方式做旧衣回收&#xff1f;别out了&#xff01;线下回收箱成本高、维护难、用户参与感弱&#xff1f;是时候用线上小程序打开全新局面了✌&#x1f4a8;线上小程序 vs 传统线下回收✅ 便捷性突破&#xff1a;线下&#xff1a;用户需亲自送至固定回收点&#x…

CD71.【C++ Dev】二叉树的三种非递归遍历方式

目录 1.知识回顾 2.前序遍历 分析 总结入栈的几种可能 循环的条件 代码 提交结果 3.中序遍历 分析 代码 提交结果 3.★后序遍历 分析 问题:如何确定是第一次访问到栈的元素还是第二次访问到栈中的元素? 方法1:使用填充的内存(依赖于架构) 判断计算机使用的架构…

音视频学习(五十九):H264中的SPS

在 H.264 (也称为 AVC, Advanced Video Coding) 视频编码标准中&#xff0c;SPS (Sequence Parameter Set) 是一个至关重要的 NALU (Network Abstraction Layer Unit) 类型&#xff0c;它承载着整个视频序列共有的全局性配置信息。你可以把它理解为视频文件的“基因”&#xff…

linux实时性研究

Linux 实时性研究旨在提升 Linux 系统对外部事件的响应速度和确定性,使其能够满足实时应用的需求。以下是关于 Linux 实时性研究的一些关键内容: Linux 实时性不足的原因 中断优先级问题:在标准 Linux 内核中,中断具有最高优先级,包括软中断,这使得实时任务的优先级得不到…

Java-面试八股文-Mysql篇

MySQL篇 1、Select 语句完整的执行顺序 难度系数&#xff1a;⭐&#x1f4cc; SQL SELECT 语句书写顺序&#xff08;开发者写的顺序&#xff09; SELECT ... FROM ... JOIN ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...&#x1f4cc; 实际执行顺序&#…

多代理系统架构:Supervisor 与 Swarm 架构详解

多代理&#xff08;Multi-Agent&#xff09;系统正成为构建复杂 AI 应用的重要范式。本文将深入剖析两种热门的多代理架构模式——Supervisor&#xff08;主管模式&#xff09;与 Swarm&#xff08;群智模式&#xff09;&#xff0c;揭示它们的执行流程、适用场景及实现细节&am…