信创环境下达梦数据库唯一索引异常无法拦截DuplicateKeyException

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 迁移到达梦数据库后,发现我们的全局异常拦截中的唯一索引异常 无法被正常拦截,给前端直接抛出了数据库原始的错误信息,对用户极其不友好。如果不对唯一索引异常拦截,则默认 与 的异常信息如下:在 中通过 注解,实现对异常响应的统一封装。可参考:全栈开发之后端脚手架:SpringBoot集成MybatisPlus代码生成,分页,雪花算法,统一响应,异常拦截,Swagger3接口文档以下是对数据库唯一索引异常的拦截,统一返回:编号不可重复。问题分析 对主流的数据库的异常进行了封装与翻译,对于 都可以进行拦截,但是到了国产数据库,比如这里是达梦8,那么其异常信息 `Spring` 就不认识

背景

迁移到达梦数据库后,发现我们的全局异常拦截中的唯一索引异常 DuplicateKeyException 无法被正常拦截,给前端直接抛出了数据库原始的错误信息,对用户极其不友好。

异常信息

如果不对唯一索引异常拦截,则默认 MySQL达梦数据库 的异常信息如下:

MySQL异常:Error updating database. Cause: java.sql. SQLIntegrityConstraintViolationException: Duplicate entry '111' for key 'XXX'

达梦8异常:Error updating database. Cause: dm.jdbc.driver. DMException: 违反表[YYY]唯一性约束

全局异常拦截

SpringBoot 中通过 RestControllerAdvice 注解,实现对异常响应的统一封装。可参考:全栈开发之后端脚手架:SpringBoot集成MybatisPlus代码生成,分页,雪花算法,统一响应,异常拦截,Swagger3接口文档

以下是对数据库唯一索引异常的拦截,统一返回:编号不可重复

@RestControllerAdvice
public class GlobalExceptionHandler
{
    /**
     * 唯一索引异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public AjaxResult handleDuplicateKeyException(DuplicateKeyException e)
    {
        return AjaxResult.error("编号不可重复");
    }
}

问题分析

Spring 对主流的数据库的异常进行了封装与翻译,对于 DuplicateKeyException 都可以进行拦截,但是到了国产数据库,比如这里是达梦8,那么其异常信息 Spring 就不认识了。

DuplicateKeyException产生过程

Spring JDBC 模块在发生数据库异常时会执行 org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator#doTranslate 方法,将不同数据库的 errorCode 进行映射,转换为自定义的框架异常。

2022-07-16-DuplicateKeyException.jpg

SQLErrorCodes生成方式

Spring 在启动时会将资源文件 org/springframework/jdbc/support/sql-error-codes.xml 生成 org.springframework.jdbc.support.SQLErrorCodes 对象,程序出现异常时将根据错误码解析并映射为 Spring 定义的数据库异常。

解决方案

在IDEA中直接双击Shift,输入 sql-error-codes 即可快速定位到这个 XML 文件。

2022-07-16-ErrorCodeXML.jpg

其中 sql-error-codes.xml 中有提示性的一句描述:"Can be overridden by definitions in a sql-error-codes.xml file - in the root of the class path." ,告诉我们这个文件可以被覆盖,直接复制该文件到对应模块的 resources 目录下,增加对应数据库需要的错误码映射。

  • 达梦里的DuplicateKeyException对应的错误码为-6602,这可以查阅达梦官方的文档。

2022-07-16-DMErrorCode.jpg

  • sql-error-codes.xml修改后放到resources目录

然后,在达梦中抛出唯一索引异常后会被翻译为 DuplicateKeyException ,可以被 Spring 拦截。

2022-07-16-ResourceFile.jpg

  • 修改后的sql-error-codes.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">

<!--
    - Default SQL error codes for well-known databases.
    - Can be overridden by definitions in a "sql-error-codes.xml" file
    - in the root of the class path.
    -
    - If the Database Product Name contains characters that are invalid
    - to use in the id attribute (like a space) then we need to add a property
    - named "databaseProductName"/"databaseProductNames" that holds this value.
    - If this property is present, then it will be used instead of the id for
    - looking up the error codes based on the current database.
    -->
<beans>

    <bean id="DB2" name="Db2" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductName">
            <value>DB2*</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>-007,-029,-097,-104,-109,-115,-128,-199,-204,-206,-301,-408,-441,-491</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>-803</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>-407,-530,-531,-532,-543,-544,-545,-603,-667</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>-904,-971</value>
        </property>
        <property name="transientDataAccessResourceCodes">
            <value>-1035,-1218,-30080,-30081</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>-911,-913</value>
        </property>
    </bean>

    <bean id="Derby" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductName">
            <value>Apache Derby</value>
        </property>
        <property name="useSqlStateForTranslation">
            <value>true</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>42802,42821,42X01,42X02,42X03,42X04,42X05,42X06,42X07,42X08</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>23505</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>22001,22005,23502,23503,23513,X0Y32</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>04501,08004,42Y07</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>40XL1</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>40001</value>
        </property>
    </bean>

    <bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="badSqlGrammarCodes">
            <value>42000,42001,42101,42102,42111,42112,42121,42122,42132</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>23001,23505</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>22001,22003,22012,22018,22025,23000,23002,23003,23502,23503,23506,23507,23513</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>90046,90100,90117,90121,90126</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>50200</value>
        </property>
    </bean>

    <!-- https://help.sap.com/saphelp_hanaplatform/helpdata/en/20/a78d3275191014b41bae7c4a46d835/content.htm -->
    <bean id="HDB" name="Hana" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductNames">
            <list>
                <value>SAP HANA</value>
                <value>SAP DB</value>
            </list>
        </property>
        <property name="badSqlGrammarCodes">
            <value>
                257,259,260,261,262,263,264,267,268,269,270,271,272,273,275,276,277,278,
                278,279,280,281,282,283,284,285,286,288,289,290,294,295,296,297,299,308,309,
                313,315,316,318,319,320,321,322,323,324,328,329,330,333,335,336,337,338,340,
                343,350,351,352,362,368
            </value>
        </property>
        <property name="permissionDeniedCodes">
            <value>10,258</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>301</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>461,462</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>-813,-709,-708,1024,1025,1026,1027,1029,1030,1031</value>
        </property>
        <property name="invalidResultSetAccessCodes">
            <value>-11210,582,587,588,594</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>131</value>
        </property>
        <property name="cannotSerializeTransactionCodes">
            <value>138,143</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>133</value>
        </property>
    </bean>

    <bean id="HSQL" name="Hsql" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductName">
            <value>HSQL Database Engine</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>-22,-28</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>-104</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>-9</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>-80</value>
        </property>
    </bean>

    <bean id="Informix" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductName">
            <value>Informix Dynamic Server</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>-201,-217,-696</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>-239,-268,-6017</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>-692,-11030</value>
        </property>
    </bean>

    <bean id="MS-SQL" name="SqlServer" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductName">
            <value>Microsoft SQL Server</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>156,170,207,208,209</value>
        </property>
        <property name="permissionDeniedCodes">
            <value>229</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>2601,2627</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>544,8114,8115</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>4060</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>1222</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>1205</value>
        </property>
    </bean>

    <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>

    <bean id="Oracle" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="badSqlGrammarCodes">
            <value>900,903,904,917,936,942,17006,6550</value>
        </property>
        <property name="invalidResultSetAccessCodes">
            <value>17003</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>1</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>1400,1722,2291,2292</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>17002,17447</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>54,30006</value>
        </property>
        <property name="cannotSerializeTransactionCodes">
            <value>8177</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>60</value>
        </property>
    </bean>

    <bean id="PostgreSQL" name="Postgres" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="useSqlStateForTranslation">
            <value>true</value>
        </property>
        <property name="badSqlGrammarCodes">
            <value>03000,42000,42601,42602,42622,42804,42P01</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>21000,23505</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>23000,23502,23503,23514</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>53000,53100,53200,53300</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>55P03</value>
        </property>
        <property name="cannotSerializeTransactionCodes">
            <value>40001</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>40P01</value>
        </property>
    </bean>

    <bean id="Sybase" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductNames">
            <list>
                <value>Sybase SQL Server</value>
                <value>Adaptive Server Enterprise</value>
                <value>ASE</value>  <!-- name as returned by jTDS driver -->
                <value>SQL Server</value>
                <value>sql server</value>  <!-- name as returned by jTDS driver -->
            </list>
        </property>
        <property name="badSqlGrammarCodes">
            <value>101,102,103,104,105,106,107,108,109,110,111,112,113,116,120,121,123,207,208,213,257,512</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>2601,2615,2626</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>233,511,515,530,546,547,2615,2714</value>
        </property>
        <property name="transientDataAccessResourceCodes">
            <value>921,1105</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>12205</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>1205</value>
        </property>
    </bean>

    <!-- 支持达梦数据库错误码-->
    <bean id="DM" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="databaseProductNames">
            <list>
                <!-- 数据源名称存在空格不能像Oracle一样直接作为beanId -->
                <value>DM DBMS</value>
            </list>
        </property>
        <property name="duplicateKeyCodes">
            <value>-6602</value>
        </property>
    </bean>
</beans>

Reference


If you have any questions or any bugs are found, please feel free to contact me.

Your comments and suggestions are welcome!

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
8天前
|
监控 数据库 索引
数据库索引的设计与优化策略
在数据库系统中,索引的设计和优化对于数据检索效率至关重要。本文探讨了数据库索引的基本概念,介绍了常见的索引类型及其适用场景,并深入分析了如何根据实际需求设计和优化索引,以提升数据库查询性能和整体系统效率。
|
6天前
|
关系型数据库 MySQL 定位技术
数据库中有哪些索引类型?
【7月更文挑战第2天】数据库中有哪些索引类型?
21 8
|
3天前
|
SQL 缓存 Java
Java中的数据库优化与索引设计
Java中的数据库优化与索引设计
|
5天前
|
数据处理 数据库 索引
数据库索引策略如何影响数据的读取效率?
【7月更文挑战第3天】数据库索引策略如何影响数据的读取效率?
7 2
|
6天前
|
存储 数据库 索引
数据库索引的作用
【7月更文挑战第2天】数据库索引的作用
12 3
|
5天前
|
存储 数据处理 数据库
数据库索引策略如何影响数据更新操作的性能?
【7月更文挑战第3天】数据库索引策略如何影响数据更新操作的性能?
8 1
|
5天前
|
监控 关系型数据库 MySQL
数据库索引策略
【7月更文挑战第3天】数据库索引策略
9 1
|
1天前
|
SQL 存储 数据库
MSSQL数据库性能调优实战:索引、查询与并发控制的深度剖析
在数据库管理领域,Microsoft SQL Server(MSSQL)的性能调优是保障业务高效运行的核心任务
|
3天前
|
存储 小程序 数据库
【微信小程序-原生开发】实用教程08 - 开通微信云开发,操作云数据库新增数据(含修改数据权限),初始化云服务(含获取微信云环境 id),获取云数据,滚动公告栏
【微信小程序-原生开发】实用教程08 - 开通微信云开发,操作云数据库新增数据(含修改数据权限),初始化云服务(含获取微信云环境 id),获取云数据,滚动公告栏
7 0
|
10天前
|
移动开发 应用服务中间件 数据库
详尽分享预发环境与生产环境共享数据库时定时任务重复执行问题解决
详尽分享预发环境与生产环境共享数据库时定时任务重复执行问题解决
10 0