初入学习 JDBC 操作数据库,想必大家都写过下面的代码:
数据库为:H2
![9868d1693992c42820485a169cbd45cf.png](https://img-blog.csdnimg.cn/img_convert/9868d1693992c42820485a169cbd45cf.png)
如果需要处理特定 SQL 异常,比如 SQL 语句错误,这个时候我们应该怎么办?
查看 SQLException 源码,我们可以发现两个重要的方法。
SQLException.getErrorCode:返回数据库特定的错误码,由数据库厂商制定,不同厂商错误码不同。如重复主键错误码在 MySQL 中是 1062,而在 Oracle 中却是 1。
SQLException.getSQLState:返回 XOPEN 或 SQL:2003 制定的错误码规范。数据库厂商会将不同错误消息映射成同一个错误码
所以我们可以根据 SQLException.getErrorCode 处理相应的数据库异常。
![a867001314a6602d88f516f539aaa13f.png](https://img-blog.csdnimg.cn/img_convert/a867001314a6602d88f516f539aaa13f.png)
由于数据库厂商错误码不相同,这就导致如果我们更换数据库,上面判断逻辑就必须重写。
下面我们使用 Spring 操作数据库。
Spring 操作数据库
![19a0826b8f69ac3b01002123e2af75ca.png](https://img-blog.csdnimg.cn/img_convert/19a0826b8f69ac3b01002123e2af75ca.png)
使用 Spring 之后,我们不再需要强制捕获异常。如果 SQL 语句运行存在异常,Spring 会抛出其内置特定的异常。如上面 SQL 语句异常将会抛出 BadSqlGrammarException 。除了这个异常之外,Spring 还定义很多数据库异常。
![e521c92962dd7c4f8e0b5fb53997d93d.png](https://img-blog.csdnimg.cn/img_convert/e521c92962dd7c4f8e0b5fb53997d93d.png)
每个 Spring 数据库异常的基类都是 DataAccessException 。由于 DataAccessException 继承自 RuntimeException ,所以在这类异常无需强制捕获。
在 Spring 中使用 SQLExceptionTranslator 进行异常转换,默认的转换规则会根据 SQLException.getErrorCode 返回的错误码进行相应的转换。
下面我们从源码分析转换过程。
实现细节
调试 JdbcTemplate 的源码。
![7779b4d51c7fdb0c427749473d7888d8.png](https://img-blog.csdnimg.cn/img_convert/7779b4d51c7fdb0c427749473d7888d8.png)
可以看到这里捕获了 SQLException ,转换之后再将其抛出。
整个转换过程,最后交给 SQLExceptionTranslator 进行转换。
首先我们查看 SQLExceptionTranslator 类图。
![68dd6fa052388168d431524fe98a91a6.png](https://img-blog.csdnimg.cn/img_convert/68dd6fa052388168d431524fe98a91a6.png)
可以看到其实现了一个抽象类以及三个子类。
![9c94b99d393622014fd3263ac8986c2a.png](https://img-blog.csdnimg.cn/img_convert/9c94b99d393622014fd3263ac8986c2a.png)
抽象类中会首先会使用子类转换,若未能转换成功,将会启动 fallback 机制,再次转换,作为兜底。
接着我们先看下三个子类的区别。
SQLErrorCodeSQLExceptionTranslator:
- 默认转换类
- 主要根据 SQLException.getErrorCode 进行转换。
- 默认使用 SQLExceptionSubclassTranslator 作为 fallback 对象。
SQLExceptionSubclassTranslator:
- 基于 JDBC 的 SQLException 标准子类判断,如 java.sql.SQLTransientException 。
- 使用 SQLStateSQLExceptionTranslator 作为 fallback 对象。
SQLStateSQLExceptionTranslator:
- 基于 SQLException.getSQLState 规则判断。
下面分析 SQLErrorCodeSQLExceptionTranslator ,其他两个比较类似,同学们可以自己看源码分析。
SQLErrorCodeSQLExceptionTranslator转换器主要根据 SQLException.getErrorCode 进行判断。Spring 默认在 org/springframework/jdbc/support/sql-error-codes.xml 归纳不同数据库厂商相关错误码。该配置文件会在第一次发生 SQL 异常时由 SQLErrorCodesFactory 进行加载,最后生成 SQLErrorCodes 。
![d7929ea90e34d0f58f804389d1340aab.png](https://img-blog.csdnimg.cn/img_convert/d7929ea90e34d0f58f804389d1340aab.png)
另外在 SQLErrorCodes 提供扩展方法,可以根据错误码转换成自定义的异常。
最后查看 SQLErrorCodeSQLExceptionTranslator 里的转换方法。
![d5fcfed62b97f1c57dc58ee3f274a4a7.png](https://img-blog.csdnimg.cn/img_convert/d5fcfed62b97f1c57dc58ee3f274a4a7.png)
前三个方法是 Spring 留下扩展方法,可以根据自己需求分别扩展。若都没有实现,将会根据错误码判断转换成具体的异常。
![24c442b7296e953765ee1975f283ac7c.png](https://img-blog.csdnimg.cn/img_convert/24c442b7296e953765ee1975f283ac7c.png)
自定义异常转换
上面说到 Spring 总共给我们留下三处扩展点。
- 继承 SQLErrorCodeSQLExceptionTranslator ,重写 customTranslate 。
- 继承 SQLExceptionTranslator ,重写 translate ,然后在 sql-error-codes.xml 注入。
- 使用 SQLErrorCodes#customTranslations ,然后在 sql-error-codes.xml 配置相关错误码转换的规则。
第三种方式改动最小,比较简单。首先在 classpath 下生成 sql-error-codes.xml ,复制原有配置,最后配置 customTranslations 。
![d521d2d2ee9781eee1b8eca3dcbf744e.png](https://img-blog.csdnimg.cn/img_convert/d521d2d2ee9781eee1b8eca3dcbf744e.png)
这里需要注意的是,需要转化的异常类型必须为 DataAccessException 子类。下面面我们自定义一个异常。
![92822440b6037f0bcd743dae6ffa3786.png](https://img-blog.csdnimg.cn/img_convert/92822440b6037f0bcd743dae6ffa3786.png)
总结
Spirng 异常处理将 SQL 异常转化成内置异常,屏蔽不同数据库返回码不一致的带来的问题。
最后总结本文的知识点,希望帮助到大家。
![d0169de6ed83ad10cc082c5ab47e235e.png](https://img-blog.csdnimg.cn/img_convert/d0169de6ed83ad10cc082c5ab47e235e.png)
欢迎工作一到五年的Java工程师朋友们加入Java程序员开发: 721575865
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!