public <T> T execute(StatementCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(obtainDataSource()); Statement stmt = null; try { stmt = con.createStatement(); applyStatementSettings(stmt); T result = action.doInStatement(stmt); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex); } finally { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } }
发生异常,代码执行到
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
打开 getExceptionTranslator():
public synchronized SQLExceptionTranslator getExceptionTranslator() { if (this.exceptionTranslator == null) { DataSource dataSource = getDataSource(); if (dataSource != null) { this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); } else { this.exceptionTranslator = new SQLStateSQLExceptionTranslator(); } } return this.exceptionTranslator; }
假设数据源不为空,执行:
this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
此时会调用SQLErrorCodeSQLExceptionTranslator对应的构造器:
public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) { this(); setDataSource(dataSource); }
该构造器先调用无参构造器:
public SQLErrorCodeSQLExceptionTranslator() { setFallbackTranslator(new SQLExceptionSubclassTranslator()); }
该构造器初始化了fallbackTranslator,使其指向SQLExceptionSubclassTranslator,
此时会调用SQLExceptionSubclassTranslator的无参构造器:
public SQLExceptionSubclassTranslator() { setFallbackTranslator(new SQLStateSQLExceptionTranslator()); }
这么做的用意是,SQLExceptionSubclassTranslator是SQLErrorCodeSQLExceptionTranslator备用转换器,SQLStateSQLExceptionTranslator是SQLExceptionSubclassTranslator的转换器,当一个异常需要转换时,先调用SQLErrorCodeSQLExceptionTranslator进行转换,SQLErrorCodeSQLExceptionTranslator无法转换时,便会调用它的备用转换器SQLExceptionSubclassTranslator进行转换,SQLExceptionSubclassTranslator也无法转换时,便会调用它的备用转换器SQLStateSQLExceptionTranslator进行转换。即
SQLErrorCodeSQLExceptionTranslator --〉SQLExceptionSubclassTranslator--〉SQLStateSQLExceptionTranslator
SQLExceptionSubclassTranslator是Spring2.5新加入的异常转换器,作用是支持JDBC4.0新增的一些SQL异常 ,适用于JDK6及以上版本。
设置完转换器,程序执行到
setDataSource(dataSource);
这个方法用于加载 SQL error code ,
public void setDataSource(DataSource dataSource) { this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource); }
SQLErrorCodesFactory是个工厂类,它的作用是加载并实例化一个名为 sql-error-codes.xml,这个文件默认放在:org.springframework.jdbc.support路径下:
这个配置文件既是个sql error code 配置文件,又是spring bean 的配置文件,这里有点一箭双雕的意思,既将sql error code用配置文件维护了起来,又可以通过Spring容器直接解析成Java Bean,供异常转换器直接使用。
sql-error-codes.xml 是可以扩展的,在SQLErrorCodesFactory的源码中有很清晰的体现:
public class SQLErrorCodesFactory { /** * The name of custom SQL error codes file, loading from the root * of the class path (e.g. from the "/WEB-INF/classes" directory). */ public static final String SQL_ERROR_CODE_OVERRIDE_PATH = "sql-error-codes.xml"; /** * The name of default SQL error code files, loading from the class path. */ public static final String SQL_ERROR_CODE_DEFAULT_PATH = "org/springframework/jdbc/support/sql-error-codes.xml";
首先,定义了两个路径变量,一个名为 SQL_ERROR_CODE_OVERRIDE_PATH,指向项目的根目录,从变量名可以看出,这个路径是供开发者扩展用的,用法就是把自定义的sql-error-codes.xml放到项目根目录下就可以了,另一个SQL_ERROR_CODE_DEFAULT_PATH 指向的就是默认路径了。
在工厂类构造器中加载并实例化SQLErrorCodes:
protected SQLErrorCodesFactory() { Map<String, SQLErrorCodes> errorCodes; try { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); lbf.setBeanClassLoader(getClass().getClassLoader()); XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf); // Load default SQL error codes. Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH); if (resource != null && resource.exists()) { bdr.loadBeanDefinitions(resource); } else { logger.warn("Default sql-error-codes.xml not found (should be included in spring.jar)"); } // Load custom SQL error codes, overriding defaults. resource = loadResource(SQL_ERROR_CODE_OVERRIDE_PATH); if (resource != null && resource.exists()) { bdr.loadBeanDefinitions(resource); logger.info("Found custom sql-error-codes.xml file at the root of the classpath"); } // Check all beans of type SQLErrorCodes. errorCodes = lbf.getBeansOfType(SQLErrorCodes.class, true, false); if (logger.isInfoEnabled()) { logger.info("SQLErrorCodes loaded: " + errorCodes.keySet()); } } catch (BeansException ex) { logger.warn("Error loading SQL error codes from config file", ex); errorCodes = Collections.emptyMap(); } this.errorCodesMap = errorCodes; }
先从默认路径加载:
Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH); if (resource != null && resource.exists()) { bdr.loadBeanDefinitions(resource); }
再从扩展路径加载:
// Load custom SQL error codes, overriding defaults. resource = loadResource(SQL_ERROR_CODE_OVERRIDE_PATH); if (resource != null && resource.exists()) { bdr.loadBeanDefinitions(resource); logger.info("Found custom sql-error-codes.xml file at the root of the classpath"); }
如果根目录存在配置文件,会覆盖默认配置 ,
实例化SQLErrorCodes:
errorCodes = lbf.getBeansOfType(SQLErrorCodes.class, true, false);
至此,SQLErrorCodeSQLException就初始化完成了。
接着调用translate 方法:
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
translate 方法定义在父类中,是个模板方法:
public DataAccessException translate(@Nullable String task, @Nullable String sql, SQLException ex) { Assert.notNull(ex, "Cannot translate a null SQLException"); if (task == null) { task = ""; } if (sql == null) { sql = ""; } DataAccessException dex = doTranslate(task, sql, ex); if (dex != null) { // Specific exception match found. return dex; } // Looking for a fallback... SQLExceptionTranslator fallback = getFallbackTranslator(); if (fallback != null) { return fallback.translate(task, sql, ex); } // We couldn't identify it more precisely. return new UncategorizedSQLException(task, sql, ex); }
先对参数进行处理,随后执行到
DataAccessException dex = doTranslate(task, sql, ex);
doTranslate是个抽象方法,由子类实现,此时的子类是SQLErrorCodeSQLExceptionTranslator,于是程序跳转到子类的doTranslate方法中,
SQLException sqlEx = ex; if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) { SQLException nestedSqlEx = sqlEx.getNextException(); if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) { logger.debug("Using nested SQLException from the BatchUpdateException"); sqlEx = nestedSqlEx; } }
如果异常是BatchUpdateException,需要通过sqlEx.getNextException()获取到SQLException,statement.executeBatch()可能会抛出BatchUpdateException,JDK API 声明未能正确执行发送到数据库的命令之一或者尝试返回结果集合,则抛BatchUpdateException.
// First, try custom translation from overridden method. DataAccessException dex = customTranslate(task, sql, sqlEx); if (dex != null) { return dex; }
customTranslate是个空方法,开发者可以继承SQLErrorCodeSQLExceptionTranslator并重写customTranslate(),然后调用jdbcTemplate.setExceptionTranslator()覆盖默认的转换器即可。
// Next, try the custom SQLException translator, if available.
if (this.sqlErrorCodes != null) {
SQLExceptionTranslator customTranslator = this.sqlErrorCodes.getCustomSqlExceptionTranslator();
if (customTranslator != null) {
DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
if (customDex != null) {
return customDex;
}
}
}
接着执行 this.sqlErrorCodes.getCustomSqlExceptionTranslator();打开getCustomSqlExceptionTranslator():
发现SQLErrorCodes中存在该属性,
@Nullable private SQLExceptionTranslator customSqlExceptionTranslator;
大致浏览了一下代码,发现和 该属性的 方法有两个:
public void setCustomSqlExceptionTranslator(@Nullable SQLExceptionTranslator customSqlExceptionTranslator) { this.customSqlExceptionTranslator = customSqlExceptionTranslator; }
和
public void setCustomSqlExceptionTranslatorClass(@Nullable Class<? extends SQLExceptionTranslator> customTranslatorClass) { if (customTranslatorClass != null) { try { this.customSqlExceptionTranslator = ReflectionUtils.accessibleConstructor(customTranslatorClass).newInstance(); } catch (Throwable ex) { throw new IllegalStateException("Unable to instantiate custom translator", ex); } } else { this.customSqlExceptionTranslator = null; } }
一个是普通的set方法,一个是可以通过class反射生成实例,这个属性也是给开发者扩展的,开发者可以在SQLErrorCodes中设置自定义的转换器实例,或者调用setCustomSqlExceptionTranslatorClass()方法传入class由框架自动生成实例。假设没有扩展,那么customTranslator为空 ,所以以下程序跳过以下程序段:
if (customTranslator != null) { DataAccessException customDex = customTranslator.translate(task, sql, sqlEx); if (customDex != null) { return customDex; } }
然后运行到:
if (this.sqlErrorCodes != null) { String errorCode; if (this.sqlErrorCodes.isUseSqlStateForTranslation()) { errorCode = sqlEx.getSQLState(); } else { // Try to find SQLException with actual error code, looping through the causes. // E.g. applicable to java.sql.DataTruncation as of JDK 1.6. SQLException current = sqlEx; while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) { current = (SQLException) current.getCause(); } errorCode = Integer.toString(current.getErrorCode()); }
有一行代码:
if (this.sqlErrorCodes.isUseSqlStateForTranslation()) { errorCode = sqlEx.getSQLState(); }
这段程序说明我们可以指定用 sql state 或者 sql error code 来转换异常,框架默认使用 sql error code ,但是有的数据库spring是用sql state 来转换的 ,例如PostgreSQL,如下配置摘自 sql-error-codes.xml:
<bean id="PostgreSQL" 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>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>
接着:
else { // Try to find SQLException with actual error code, looping through the causes. // E.g. applicable to java.sql.DataTruncation as of JDK 1.6. SQLException current = sqlEx; while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) { current = (SQLException) current.getCause(); } errorCode = Integer.toString(current.getErrorCode()); }
这里也是因为有些方法可能抛出SQLException的子异常 ,作者用while循环读取当前异常的上一个异常,直到找到SQLException为止,这样做的好处是,假如以后有新增的子类异常,都可以通过循环找到SQLException,举个栗子,例如 DataTruncation,它的继承体系是:
-- java.sql.SQLException
-- java.sql.SQLWarning
-- java.sql.DataTruncation
第一次循环找到的是 java.sql.SQLWarning,第二次循环的时候就找到SQLException了。然后获取error。接着:
if (errorCode != null) { // Look for defined custom translations first. CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations(); if (customTranslations != null) { for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) { if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) { if (customTranslation.getExceptionClass() != null) { DataAccessException customException = createCustomException( task, sql, sqlEx, customTranslation.getExceptionClass()); if (customException != null) { logTranslation(task, sql, sqlEx, true); return customException; } } } } }首先:
CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations();
打开this.sqlErrorCodes.getCustomTranslations()
发现是一个数组:
@Nullable public CustomSQLErrorCodesTranslation[] getCustomTranslations() { return this.customTranslations; }
接着打开:
/** * JavaBean for holding custom JDBC error codes translation for a particular * database. The "exceptionClass" property defines which exception will be * thrown for the list of error codes specified in the errorCodes property. * * @author Thomas Risberg * @since 1.1 * @see SQLErrorCodeSQLExceptionTranslator */ public class CustomSQLErrorCodesTranslation {
从注释信息来看,这个类也是用来扩展用的,这个类有两个属性:
private String[] errorCodes = new String[0]; @Nullable private Class<?> exceptionClass;
errorCodes和exceptionClass,看来,是一组errorCodes对应一个异常类,
这个异常类 用class<?>修饰,但是往下阅读代码的时候发现这个方法:
/** * Set the exception class for the specified error codes. */ public void setExceptionClass(@Nullable Class<?> exceptionClass) { if (exceptionClass != null && !DataAccessException.class.isAssignableFrom(exceptionClass)) { throw new IllegalArgumentException("Invalid exception class [" + exceptionClass + "]: needs to be a subclass of [org.springframework.dao.DataAccessException]"); } this.exceptionClass = exceptionClass; }
从trhow语句来看,这个类应该必须实现DataAccessException,这时,开发者就可以通过调用SQLErrorCodeSQLExceptionTranslator.getSqlErrorCodes().setCustomTranslations()设置自定义异常转换逻辑。
接着:
if (customTranslations != null) { for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) { if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) {
如果存在自定义的CustomSQLErrorCodesTranslation,并且数据库返回的SQLErrorCode和CustomSQLErrorCodesTranslation的SQLErrorCodes中的某个code相匹配,就执行:
if (customTranslation.getExceptionClass() != null) { DataAccessException customException = createCustomException( task, sql, sqlEx, customTranslation.getExceptionClass()); if (customException != null) { logTranslation(task, sql, sqlEx, true); return customException; } }
这时,customTranslation.getExceptionClass() != null不为空,执行
DataAccessException customException = createCustomException( task, sql, sqlEx, customTranslation.getExceptionClass());
createCustomException方法通过反射构造器生成自定义的异常类,并返回。
如果不存在自定义的CustomSQLErrorCodesTranslation,执行:
// Next, look for grouped error codes. if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new BadSqlGrammarException(task, sql, sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new InvalidResultSetAccessException(task, sql, sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx); } else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) { logTranslation(task, sql, sqlEx, false); return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx); } } }
以上代码就是SQLErrorCodeSQLExceptionTranslator转换SQLException的核心逻辑,通过Arrays.binarySearch()一一到Spring JDBC 异常体系定义的code中查找,然后返回对应的异常类。
假如以上代码都没有找到,则执行:
return null;
这时,程序重新流转回AbstractFallbackSQLExceptionTranslator中的translate方法中,
@Override public DataAccessException translate(@Nullable String task, @Nullable String sql, SQLException ex) { Assert.notNull(ex, "Cannot translate a null SQLException"); if (task == null) { task = ""; } if (sql == null) { sql = ""; } DataAccessException dex = doTranslate(task, sql, ex); if (dex != null) { // !!! // Specific exception match found. return dex; } // Looking for a fallback... SQLExceptionTranslator fallback = getFallbackTranslator(); if (fallback != null) { return fallback.translate(task, sql, ex); } // We couldn't identify it more precisely. return new UncategorizedSQLException(task, sql, ex); }
此时,叹号部分不成立,程序往下走:
SQLExceptionTranslator fallback = getFallbackTranslator();
该行代码是获取SQLErrorCodeSQLExceptionTranslator的备用转换器,前面我们提到,SQLErrorCodeSQLExceptionTranslator在初始化时备份了一个SQLExceptionSubclassTranslator转换器,那么这个方法返回的就是SQLExceptionSubclassTranslator的实例。
接着:
if (fallback != null) { return fallback.translate(task, sql, ex); }
此时fallback!=null,执行:
return fallback.translate(task, sql, ex);
程序重新流转回AbstractFallbackSQLExceptionTranslator中的translate方法中,接着:
DataAccessException dex = doTranslate(task, sql, ex);
执行SQLExceptionSubclassTranslator的doTranslate方法,该方法比较简单,下面贴出:
protected DataAccessException doTranslate(String task, String sql, SQLException ex) { if (ex instanceof SQLTransientException) { if (ex instanceof SQLTransientConnectionException) { return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLTransactionRollbackException) { return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLTimeoutException) { return new QueryTimeoutException(buildMessage(task, sql, ex), ex); } } else if (ex instanceof SQLNonTransientException) { if (ex instanceof SQLNonTransientConnectionException) { return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLDataException) { return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLIntegrityConstraintViolationException) { return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLInvalidAuthorizationSpecException) { return new PermissionDeniedDataAccessException(buildMessage(task, sql, ex), ex); } else if (ex instanceof SQLSyntaxErrorException) { return new BadSqlGrammarException(task, sql, ex); } else if (ex instanceof SQLFeatureNotSupportedException) { return new InvalidDataAccessApiUsageException(buildMessage(task, sql, ex), ex); } } else if (ex instanceof SQLRecoverableException) { return new RecoverableDataAccessException(buildMessage(task, sql, ex), ex); } // Fallback to Spring's own SQL state translation... return null; }
这个类就不用判断错误码了,直接判断异常的类型,进而转换成sping jdbc 的异常类型,如果匹配到,返回该类型的实例。
如果匹配不到返回null,
程序再一次流转回AbstractFallbackSQLExceptionTranslator中的translate方法中的叹号部分:
@Override public DataAccessException translate(@Nullable String task, @Nullable String sql, SQLException ex) { Assert.notNull(ex, "Cannot translate a null SQLException"); if (task == null) { task = ""; } if (sql == null) { sql = ""; } DataAccessException dex = doTranslate(task, sql, ex); if (dex != null) { // !!! // Specific exception match found. return dex; } // Looking for a fallback... SQLExceptionTranslator fallback = getFallbackTranslator(); if (fallback != null) { return fallback.translate(task, sql, ex); } // We couldn't identify it more precisely. return new UncategorizedSQLException(task, sql, ex); }
此时dex为null,继续往下执行:
SQLExceptionTranslator fallback = getFallbackTranslator(); if (fallback != null) { return fallback.translate(task, sql, ex); }
SQLExceptionSubclassTranslatord的备用转换器是SQLStateSQLExceptionTranslator,程序又一次流转回AbstractFallbackSQLExceptionTranslator中的translate方法中的叹号部分:
public DataAccessException translate(@Nullable String task, @Nullable String sql, SQLException ex) {
Assert.notNull(ex, "Cannot translate a null SQLException");
if (task == null) {
task = "";
}
if (sql == null) {
sql = "";
}
DataAccessException dex = doTranslate(task, sql, ex); //!!!
if (dex != null) {
// Specific exception match found.
return dex;
}
// Looking for a fallback...
SQLExceptionTranslator fallback = getFallbackTranslator();
if (fallback != null) {
return fallback.translate(task, sql, ex);
}
// We couldn't identify it more precisely.
return new UncategorizedSQLException(task, sql, ex);
}
此时,执行SQLStateSQLExceptionTranslator的doTranslate方法:
protected DataAccessException doTranslate(String task, String sql, SQLException ex) { // First, the getSQLState check... String sqlState = getSqlState(ex); if (sqlState != null && sqlState.length() >= 2) { String classCode = sqlState.substring(0, 2); if (logger.isDebugEnabled()) { logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'"); } if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) { return new BadSqlGrammarException(task, sql, ex); } else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) { return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); } else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } else if (CONCURRENCY_FAILURE_CODES.contains(classCode)) { return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); } } // For MySQL: exception class name indicating a timeout? // (since MySQL doesn't throw the JDBC 4 SQLTimeoutException) if (ex.getClass().getName().contains("Timeout")) { return new QueryTimeoutException(buildMessage(task, sql, ex), ex); } // Couldn't resolve anything proper - resort to UncategorizedSQLException. return null; }
首先,执行
String sqlState = getSqlState(ex); if (sqlState != null && sqlState.length() >= 2) {
获取sql state,接着:
String classCode = sqlState.substring(0, 2); if (logger.isDebugEnabled()) { logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'"); } if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) { return new BadSqlGrammarException(task, sql, ex); } else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) { return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex); } else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) { return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex); } else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) { return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex); } else if (CONCURRENCY_FAILURE_CODES.contains(classCode)) { return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex); }
根据sql state 进行转换,sql state 的值用静态变量定义在该类里:
private static final Set<String> BAD_SQL_GRAMMAR_CODES = new HashSet<>(8); private static final Set<String> DATA_INTEGRITY_VIOLATION_CODES = new HashSet<>(8); private static final Set<String> DATA_ACCESS_RESOURCE_FAILURE_CODES = new HashSet<>(8); private static final Set<String> TRANSIENT_DATA_ACCESS_RESOURCE_CODES = new HashSet<>(8); private static final Set<String> CONCURRENCY_FAILURE_CODES = new HashSet<>(4); static { BAD_SQL_GRAMMAR_CODES.add("07"); // Dynamic SQL error BAD_SQL_GRAMMAR_CODES.add("21"); // Cardinality violation BAD_SQL_GRAMMAR_CODES.add("2A"); // Syntax error direct SQL BAD_SQL_GRAMMAR_CODES.add("37"); // Syntax error dynamic SQL BAD_SQL_GRAMMAR_CODES.add("42"); // General SQL syntax error BAD_SQL_GRAMMAR_CODES.add("65"); // Oracle: unknown identifier DATA_INTEGRITY_VIOLATION_CODES.add("01"); // Data truncation DATA_INTEGRITY_VIOLATION_CODES.add("02"); // No data found DATA_INTEGRITY_VIOLATION_CODES.add("22"); // Value out of range DATA_INTEGRITY_VIOLATION_CODES.add("23"); // Integrity constraint violation DATA_INTEGRITY_VIOLATION_CODES.add("27"); // Triggered data change violation DATA_INTEGRITY_VIOLATION_CODES.add("44"); // With check violation DATA_ACCESS_RESOURCE_FAILURE_CODES.add("08"); // Connection exception DATA_ACCESS_RESOURCE_FAILURE_CODES.add("53"); // PostgreSQL: insufficient resources (e.g. disk full) DATA_ACCESS_RESOURCE_FAILURE_CODES.add("54"); // PostgreSQL: program limit exceeded (e.g. statement too complex) DATA_ACCESS_RESOURCE_FAILURE_CODES.add("57"); // DB2: out-of-memory exception / database not started DATA_ACCESS_RESOURCE_FAILURE_CODES.add("58"); // DB2: unexpected system error TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JW"); // Sybase: internal I/O error TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JZ"); // Sybase: unexpected I/O error TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("S1"); // DB2: communication failure CONCURRENCY_FAILURE_CODES.add("40"); // Transaction rollback CONCURRENCY_FAILURE_CODES.add("61"); // Oracle: deadlock }
如果匹配到,返回对应的异常实例,如果匹配不到,接着有点特殊处理:
// For MySQL: exception class name indicating a timeout? // (since MySQL doesn't throw the JDBC 4 SQLTimeoutException) if (ex.getClass().getName().contains("Timeout")) { return new QueryTimeoutException(buildMessage(task, sql, ex), ex); }
意思大概是 mysql不支持 JDBC 4.0 规范定义SQLTimeoutException异常,只好用类名是否包含Timeout字样来判断。如果包含,则抛出QueryTimeoutException异常。否则返回空,程序流转回AbstractFallbackSQLExceptionTranslator中的translate方法中的叹号部分:
public DataAccessException translate(@Nullable String task, @Nullable String sql, SQLException ex) { Assert.notNull(ex, "Cannot translate a null SQLException"); if (task == null) { task = ""; } if (sql == null) { sql = ""; } DataAccessException dex = doTranslate(task, sql, ex); if (dex != null) { //!!! // Specific exception match found. return dex; } // Looking for a fallback... SQLExceptionTranslator fallback = getFallbackTranslator(); if (fallback != null) { return fallback.translate(task, sql, ex); } // We couldn't identify it more precisely. return new UncategorizedSQLException(task, sql, ex); }
此时,dex为空,执行到:
SQLExceptionTranslator fallback = getFallbackTranslator();
SQLStateSQLExceptionTranslator没有备用转换器,fallback=null,到此,转换工作就完成了,大概的流程就是先由 SQLErrorCodeSQLExceptionTranslator 进行转换,如果转换不到,再由SQLExceptionSubclassTranslator进行转换,如果转换不到,再由SQLStateSQLExceptionTranslator进行转换,如果还转换不到程序抛出异常。可以从以上的分析得出结论,3个转换器中,SQLErrorCodeSQLExceptionTranslator是最复杂的,功能也最强大的,有资料显示,sql error code 比sql state准确很多,而SQLExceptionSubclassTranslator也只是对JDBC4.0规范的的补充,SQLErrorCodeSQLExceptionTranslator为开发者开放了多个接口供开发者扩展,实在是用心良苦。