项目某需求需要通过程序定时将数据从A系统同步至B、C等系统。数据同步过程中常见的一种错误是是唯一性冲突(主键或者其它唯一约束),开发中一般直接使用org.springframework.dao.DuplicateKeyException判断冲突是否出现了唯一性冲突,如果是则忽略该条数据,但是使用国产达梦数据库后该判断方式失效了,默认返回了dm.jdbc.driver.DMException。因此需要将达梦等国产数据库错误码适配Spring
String sql="insert into TABLE(EMPLOYEEID, EMPLOYEENAME, IDENTITYCARD) values(?,?,?)";
List params = new ArrayList();
try {
for(int i=0;i<1000;i++){
Object param[]={"001", "adaivskenan", "140000202201018888"};
params.add(param);
}
jdbcTemplate.batchUpdate(sql,params);
} catch (Exception e) {
//判断批量异常,退化为单条处理
if(NestedExceptionUtils.getRootCause(e).equals(BatchUpdateException.class)){
for(int i=0;i<1000;i++){
Object param[]={"001", "adaivskenan", "140000202201018888"};
params.add(param);
try{
jdbcTemplate.update(sql,params);
}catch(DuplicateKeyException e){
//忽略唯一索引错误
}
}
}
在资源目录下放置sql-error-codes.xml文件,增加数据库错误码映射
Spring JDBC模块发生数据库异常时会执行org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator#doTranslate方法,将不同数据库的errorCode进行翻译,转换为自定义的框架异常,如下
// 根据errorCode通过sqlErrorCodes判断是否为DuplicateKey冲突,转换为DuplicateKeyException
else if (Arrays.binarySearch(sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
logTranslation(task, sql, sqlEx, false);
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
}
Spring服务启动时将资源该资源文件org/springframework/jdbc/support/sql-error-codes.xml生成org.springframework.jdbc.support.SQLErrorCodes对象,程序出现异常时将根据错误码解析并转义为Spring定义的数据库异常。
// 解析数据库自定义错误码
//org.springframework.jdbc.support.SQLErrorCodesFactory#resolveErrorCodes
synchronized (this.dataSourceCache) {
// Double-check within full dataSourceCache lock
sec = this.dataSourceCache.get(dataSource);
if (sec == null) {
// We could not find it - got to look it up.
try {
// 获取数据库名称如Orcle、MariaDB、MySQL等
String name = JdbcUtils.extractDatabaseMetaData(dataSource,
DatabaseMetaData::getDatabaseProductName);
if (StringUtils.hasLength(name)) {
return registerDatabase(dataSource, name);
}
}
catch (MetaDataAccessException ex) {
logger.warn("Error while extracting database name", ex);
}
return null;
}
}
// 注册数据库并将SQLErrorCodoes与之对应
//org.springframework.jdbc.support.SQLErrorCodesFactory#registerDatabase
SQLErrorCodes sec = getErrorCodes(databaseName);
if (logger.isDebugEnabled()) {
logger.debug("Caching SQL error codes for DataSource [" + identify(dataSource) +
"]: database product name is '" + databaseName + "'");
}
this.dataSourceCache.put(dataSource, sec);
return sec;
sql-error-codes.xml中关键的一句描述" Can be overridden by definitions in a “sql-error-codes.xml” file - in the root of the class path." 。该文件可以被覆盖,直接复制该文件增加对应数据库需要的错误码映射。
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<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,-491value>
property>
<property name="duplicateKeyCodes">
<value>-803value>
property>
<property name="dataIntegrityViolationCodes">
<value>-407,-530,-531,-532,-543,-544,-545,-603,-667value>
property>
<property name="dataAccessResourceFailureCodes">
<value>-904,-971value>
property>
<property name="transientDataAccessResourceCodes">
<value>-1035,-1218,-30080,-30081value>
property>
<property name="deadlockLoserCodes">
<value>-911,-913value>
property>
bean>
<bean id="Derby" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Apache Derbyvalue>
property>
<property name="useSqlStateForTranslation">
<value>truevalue>
property>
<property name="badSqlGrammarCodes">
<value>42802,42821,42X01,42X02,42X03,42X04,42X05,42X06,42X07,42X08value>
property>
<property name="duplicateKeyCodes">
<value>23505value>
property>
<property name="dataIntegrityViolationCodes">
<value>22001,22005,23502,23503,23513,X0Y32value>
property>
<property name="dataAccessResourceFailureCodes">
<value>04501,08004,42Y07value>
property>
<property name="cannotAcquireLockCodes">
<value>40XL1value>
property>
<property name="deadlockLoserCodes">
<value>40001value>
property>
bean>
<bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>42000,42001,42101,42102,42111,42112,42121,42122,42132value>
property>
<property name="duplicateKeyCodes">
<value>23001,23505value>
property>
<property name="dataIntegrityViolationCodes">
<value>22001,22003,22012,22018,22025,23000,23002,23003,23502,23503,23506,23507,23513value>
property>
<property name="dataAccessResourceFailureCodes">
<value>90046,90100,90117,90121,90126value>
property>
<property name="cannotAcquireLockCodes">
<value>50200value>
property>
bean>
<bean id="HDB" name="Hana" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>SAP HANAvalue>
<value>SAP DBvalue>
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,258value>
property>
<property name="duplicateKeyCodes">
<value>301value>
property>
<property name="dataIntegrityViolationCodes">
<value>461,462value>
property>
<property name="dataAccessResourceFailureCodes">
<value>-813,-709,-708,1024,1025,1026,1027,1029,1030,1031value>
property>
<property name="invalidResultSetAccessCodes">
<value>-11210,582,587,588,594value>
property>
<property name="cannotAcquireLockCodes">
<value>131value>
property>
<property name="cannotSerializeTransactionCodes">
<value>138,143value>
property>
<property name="deadlockLoserCodes">
<value>133value>
property>
bean>
<bean id="HSQL" name="Hsql" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>HSQL Database Enginevalue>
property>
<property name="badSqlGrammarCodes">
<value>-22,-28value>
property>
<property name="duplicateKeyCodes">
<value>-104value>
property>
<property name="dataIntegrityViolationCodes">
<value>-9value>
property>
<property name="dataAccessResourceFailureCodes">
<value>-80value>
property>
bean>
<bean id="Informix" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Informix Dynamic Servervalue>
property>
<property name="badSqlGrammarCodes">
<value>-201,-217,-696value>
property>
<property name="duplicateKeyCodes">
<value>-239,-268,-6017value>
property>
<property name="dataIntegrityViolationCodes">
<value>-692,-11030value>
property>
bean>
<bean id="MS-SQL" name="SqlServer" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductName">
<value>Microsoft SQL Servervalue>
property>
<property name="badSqlGrammarCodes">
<value>156,170,207,208,209value>
property>
<property name="permissionDeniedCodes">
<value>229value>
property>
<property name="duplicateKeyCodes">
<value>2601,2627value>
property>
<property name="dataIntegrityViolationCodes">
<value>544,8114,8115value>
property>
<property name="dataAccessResourceFailureCodes">
<value>4060value>
property>
<property name="cannotAcquireLockCodes">
<value>1222value>
property>
<property name="deadlockLoserCodes">
<value>1205value>
property>
bean>
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>MySQLvalue>
<value>MariaDBvalue>
list>
property>
<property name="badSqlGrammarCodes">
<value>1054,1064,1146value>
property>
<property name="duplicateKeyCodes">
<value>1062value>
property>
<property name="dataIntegrityViolationCodes">
<value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557value>
property>
<property name="dataAccessResourceFailureCodes">
<value>1value>
property>
<property name="cannotAcquireLockCodes">
<value>1205,3572value>
property>
<property name="deadlockLoserCodes">
<value>1213value>
property>
bean>
<bean id="Oracle" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="badSqlGrammarCodes">
<value>900,903,904,917,936,942,17006,6550value>
property>
<property name="invalidResultSetAccessCodes">
<value>17003value>
property>
<property name="duplicateKeyCodes">
<value>1value>
property>
<property name="dataIntegrityViolationCodes">
<value>1400,1722,2291,2292value>
property>
<property name="dataAccessResourceFailureCodes">
<value>17002,17447value>
property>
<property name="cannotAcquireLockCodes">
<value>54,30006value>
property>
<property name="cannotSerializeTransactionCodes">
<value>8177value>
property>
<property name="deadlockLoserCodes">
<value>60value>
property>
bean>
<bean id="PostgreSQL" name="Postgres" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="useSqlStateForTranslation">
<value>truevalue>
property>
<property name="badSqlGrammarCodes">
<value>03000,42000,42601,42602,42622,42804,42P01value>
property>
<property name="duplicateKeyCodes">
<value>21000,23505value>
property>
<property name="dataIntegrityViolationCodes">
<value>23000,23502,23503,23514value>
property>
<property name="dataAccessResourceFailureCodes">
<value>53000,53100,53200,53300value>
property>
<property name="cannotAcquireLockCodes">
<value>55P03value>
property>
<property name="cannotSerializeTransactionCodes">
<value>40001value>
property>
<property name="deadlockLoserCodes">
<value>40P01value>
property>
bean>
<bean id="Sybase" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>Sybase SQL Servervalue>
<value>Adaptive Server Enterprisevalue>
<value>ASEvalue>
<value>SQL Servervalue>
<value>sql servervalue>
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,512value>
property>
<property name="duplicateKeyCodes">
<value>2601,2615,2626value>
property>
<property name="dataIntegrityViolationCodes">
<value>233,511,515,530,546,547,2615,2714value>
property>
<property name="transientDataAccessResourceCodes">
<value>921,1105value>
property>
<property name="cannotAcquireLockCodes">
<value>12205value>
property>
<property name="deadlockLoserCodes">
<value>1205value>
property>
bean>
<bean id="DM" class="org.springframework.jdbc.support.SQLErrorCodes">
<property name="databaseProductNames">
<list>
<value>DM DBMSvalue>
list>
property>
<property name="duplicateKeyCodes">
<value>-6602value>
property>
bean>
beans>