从源码解析 Spring JDBC 异常抽象

初入学习 JDBC 操作数据库,想必大家都写过下面的代码:

数据库为:H2

1558763379012ad6cfc3d5bf34e2d8b249fbc21ded14a.png

如果需要处理特定 SQL 异常,比如 SQL 语句错误,这个时候我们应该怎么办?

查看 SQLException 源码,我们可以发现两个重要的方法。

SQLException.getErrorCode:返回数据库特定的错误码,由数据库厂商制定,不同厂商错误码不同。如重复主键错误码在 MySQL 中是 1062,而在 Oracle 中却是 1。

SQLException.getSQLState:返回 XOPENSQL:2003 制定的错误码规范。数据库厂商会将不同错误消息映射成同一个错误码

所以我们可以根据 SQLException.getErrorCode 处理相应的数据库异常。

15587636977529093ef116cbd4a4e9038656c7e697778.png

由于数据库厂商错误码不相同,这就导致如果我们更换数据库,上面判断逻辑就必须重写。

下面我们使用 Spring 操作数据库。

Spring 操作数据库

155876376989026d5d088813443e184fef4d247fe79e9.png

使用 Spring 之后,我们不再需要强制捕获异常。如果 SQL 语句运行存在异常,Spring 会抛出其内置特定的异常。如上面 SQL 语句异常将会抛出 BadSqlGrammarException。除了这个异常之外,Spring 还定义很多数据库异常。

Spring 数据库异常

每个 Spring 数据库异常的基类都是 DataAccessException。由于 DataAccessException 继承自 RuntimeException,所以在这类异常无需强制捕获。

在 Spring 中使用 SQLExceptionTranslator 进行异常转换,默认的转换规则会根据 SQLException.getErrorCode 返回的错误码进行相应的转换。

下面我们从源码分析转换过程。

实现细节

调试 JdbcTemplate 的源码。

155876425383535e0fb8314914f4aa9fd86d97d2c212b.png

可以看到这里捕获了 SQLException,转换之后再将其抛出。

整个转换过程,最后交给 SQLExceptionTranslator 进行转换。

首先我们查看 SQLExceptionTranslator 类图。

1558764498907d72f41852e874c6ca6b370216f8f0243.png

可以看到其实现了一个抽象类以及三个子类。

1558764729835900605b4b26f45fa97c9a565ea6402c5.png

抽象类中会首先会使用子类转换,若未能转换成功,将会启动 fallback机制,再次转换,作为兜底。

接着我们先看下三个子类的区别。

SQLErrorCodeSQLExceptionTranslator:

  1. 默认转换类
  2. 主要根据 SQLException.getErrorCode 进行转换。
  3. 默认使用 SQLExceptionSubclassTranslator 作为 fallback 对象。

SQLExceptionSubclassTranslator

  1. 基于 JDBC 的 SQLException 标准子类判断,如 java.sql.SQLTransientException
  2. 使用 SQLStateSQLExceptionTranslator 作为 fallback 对象。

SQLStateSQLExceptionTranslator

  1. 基于 SQLException.getSQLState 规则判断。

下面分析 SQLErrorCodeSQLExceptionTranslator ,其他两个比较类似,同学们可以自己看源码分析。

SQLErrorCodeSQLExceptionTranslator 转换器主要根据 SQLException.getErrorCode 进行判断。Spring 默认在 org/springframework/jdbc/support/sql-error-codes.xml 归纳不同数据库厂商相关错误码。该配置文件会在第一次发生 SQL 异常时由 SQLErrorCodesFactory 进行加载,最后生成 SQLErrorCodes

15587650308278d7e4c1ddce64b8399d284f7de62653c.png

另外在 SQLErrorCodes 提供扩展方法,可以根据错误码转换成自定义的异常。

最后查看 SQLErrorCodeSQLExceptionTranslator 里的转换方法。

155876551332300ff7f1c4bc04d70ace8d5ee9e9dfadb.png

前三个方法是 Spring 留下扩展方法,可以根据自己需求分别扩展。若都没有实现,将会根据错误码判断转换成具体的异常。

1558765591114e6e8e9f273da4cd2a3fcbf95eabbd449.png

自定义异常转换

上面说到 Spring 总共给我们留下三处扩展点。

  1. 继承 SQLErrorCodeSQLExceptionTranslator,重写 customTranslate
  2. 继承 SQLExceptionTranslator,重写 translate,然后在 sql-error-codes.xml注入。
  3. 使用 SQLErrorCodes#customTranslations ,然后在 sql-error-codes.xml 配置相关错误码转换的规则。

第三种方式改动最小,比较简单。首先在 classpath 下生成 sql-error-codes.xml,复制原有配置,最后配置 customTranslations

1558765757759d187567a9d4e46d98d0ad7a92ce47fc8.png

这里需要注意的是,需要转化的异常类型必须为 DataAccessException 子类。下面面我们自定义一个异常。

155876581690994d8583832b849b5be65e40e32299558.png

总结

Spirng 异常处理将 SQL 异常转化成内置异常,屏蔽不同数据库返回码不一致的带来的问题。

最后总结本文的知识点,希望帮助到大家。

155876696761376682d3e84f647d7a70aadd6811f111f.png

帮助

Handling SQLExceptions

posted @ 2019-05-25 15:17  楼下小黑哥  阅读(1150)  评论(0编辑  收藏  举报