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

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 迁移到达梦数据库后,发现我们的全局异常拦截中的唯一索引异常 无法被正常拦截,给前端直接抛出了数据库原始的错误信息,对用户极其不友好。如果不对唯一索引异常拦截,则默认 与 的异常信息如下:在 中通过 注解,实现对异常响应的统一封装。可参考:全栈开发之后端脚手架: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!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
4月前
|
存储 关系型数据库 MySQL
MySQL数据库索引的数据结构?
MySQL中默认使用B+tree索引,它是一种多路平衡搜索树,具有树高较低、检索速度快的特点。所有数据存储在叶子节点,非叶子节点仅作索引,且叶子节点形成双向链表,便于区间查询。
175 4
|
4月前
|
数据可视化 BI API
无缝对接云数据库:自定义报表生成工具在混合云环境下的部署指南
自定义报表生成工具通过拖拽设计、多数据源整合及自动化输出,帮助业务人员零代码创建个性化报表,解决传统工具灵活性不足、技术门槛高的问题。文章对比其与传统报表差异,列举行业应用场景(如财务、零售),并给出选型建议与主流工具(如FineReport、Power BI、板栗看板)的优劣势分析。
209 0
|
7月前
|
Java 关系型数据库 MySQL
JDK、Tomcat、MariaDB数据库和Profile多环境的配置与使用
以上就是JDK、Tomcat、MariaDB数据库和Profile多环境的配置与使用的基本步骤。这些步骤可能会因为你的具体需求和环境而有所不同,但是基本的思路是一样的。希望这些信息能够帮助你更好地理解和使用这些工具。
258 17
|
5月前
|
存储 算法 关系型数据库
数据库主键与索引详解
本文介绍了主键与索引的核心特性及其区别。主键具有唯一标识、数量限制、存储类型和自动排序等特点,用于确保数据完整性和提升查询效率;而索引通过特殊数据结构(如B+树、哈希)优化查询速度,适用于不同场景。文章分析了主键与索引的优劣、适用场景及工作原理,并对比两者在唯一性、数量限制、功能定位等方面的差异,为数据库设计提供指导。
|
8月前
|
存储 缓存 数据库
数据库索引采用B+树不采用B树的原因?
● B+树更便于遍历:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。 ● B+树的磁盘读写代价更低:B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。 ● B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条
|
8月前
|
数据库连接 PHP 数据库
【YashanDB知识库】PHP使用ODBC使用数据库绑定参数功能异常
【YashanDB知识库】PHP使用ODBC使用数据库绑定参数功能异常
|
8月前
|
PHP 数据库
【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常
【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常
|
8月前
|
数据库
【YashanDB知识库】数据库审计shutdown immediate操作导致数据库异常退出
【YashanDB知识库】数据库审计shutdown immediate操作导致数据库异常退出
|
8月前
|
数据库
【YashanDB知识库】调整NUMBER精度,再执行统计信息收集高级包偶现数据库异常退出
【问题分类】功能使用 【关键字】NUMBER类型精度修改,统计信息收集 【问题描述】调整表中NUMBER字段精度时,数据库在收集统计信息时会异常退出。原因是YashanDB处理NUMBER字段精度时误踩内存。 【解决方法】锁定涉及NUMBER精度调整的表的统计信息;升级数据库至22.2.13.100及以上版本。 【影响范围】22.2.13.100以前版本。
|
11月前
|
存储 缓存 数据库
数据库索引采用B+树不采用B树的原因?
B+树优化了数据存储和查询效率,数据仅存于叶子节点,便于区间查询和遍历,磁盘读写成本低,查询效率稳定,特别适合数据库索引及范围查询。
151 6

热门文章

最新文章

下一篇
oss云网关配置