问题
最近公司想把原Oracle数据库都迁移到Mysql,这个切换需要一段时间过渡,所以存在Oracle、Mysql在项目中同时使用的情况。这样就需要使用多数据源的技术。多数据源配置本身比较简单,但有一个场景出现了问题。考虑如下代码:
// 通过try-catch实现insertOrUpdate Data data = new Data(); try{ dataMapper.insert(data); } catch (DuplicateKeyException e) { dataMapper.update(data); }
可是意外发生了,这里DuplicateKeyException异常并没有被捕获,或者说这里抛出的异常并不是我们想要捕获的,而是抛出的DataAccessResourceFailureException异常,异常栈信息片段如下。
org.springframework.dao.DataAccessResourceFailureException: ### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (...) violated ### The error may involve DataMapper.insert-Inline ### The error occurred while setting parameters ### SQL: INSERT INTO ... VALUES (?, ?, ?, ?, ?) ### Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (PBOC.PK_PB_NDES_DATA_RELATION) violated ; SQL []; ORA-00001: unique constraint (...) violated ; nested exception is java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (...) violated at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:251) ~[spring-jdbc-4.2.0.RELEASE.jar:4.2.0.RELEASE] at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73) ~[spring-jdbc-4.2.0.RELEASE.jar:4.2.0.RELEASE] at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73) ~[mybatis-spring-1.2.2.jar:1.2.2] at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:371) ~[mybatis-spring-1.2.2.jar:1.2.2] at com.sun.proxy.$Proxy29.insert(Unknown Source) ~[na:na] at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:240) ~[mybatis-spring-1.2.2.jar:1.2.2]
排查问题
打开Spring源码 DataAccessResourceFailureException这个类,看了一眼注释,发现跟预期完全不对路啊。上面的异常栈已经说了,driver层给出的异常 java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (…) violated 说明这个异常确实是唯一键冲突,但到spring这里异常类型出问题了。
/** * Data access exception thrown when a resource fails completely: * for example, if we can't connect to a database using JDBC. * * @author Rod Johnson * @author Thomas Risberg */ @SuppressWarnings("serial") public class DataAccessResourceFailureException extends NonTransientDataAccessResourceException {
没办法,只能去看spring在异常转换的逻辑了,先根据异常栈定位到SQLErrorCodeSQLExceptionTranslator,很快就找到了如下代码片段。
else if (Arrays.binarySearch(this.sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false