当前位置: 代码迷 >> 综合 >> Spring5:就这一次,搞定JDBC异常转换器之异常转换过程简析
  详细解决方案

Spring5:就这一次,搞定JDBC异常转换器之异常转换过程简析

热度:20   发布时间:2024-01-16 14:15:08.0
假设调用以下方法
	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中存在该属性,

	@Nullableprivate 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()

发现是一个数组:

	@Nullablepublic 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];@Nullableprivate 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方法中,

	@Overridepublic 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方法中的叹号部分:

	@Overridepublic 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 errorBAD_SQL_GRAMMAR_CODES.add("21");	// Cardinality violationBAD_SQL_GRAMMAR_CODES.add("2A");	// Syntax error direct SQLBAD_SQL_GRAMMAR_CODES.add("37");	// Syntax error dynamic SQLBAD_SQL_GRAMMAR_CODES.add("42");	// General SQL syntax errorBAD_SQL_GRAMMAR_CODES.add("65");	// Oracle: unknown identifierDATA_INTEGRITY_VIOLATION_CODES.add("01");	// Data truncationDATA_INTEGRITY_VIOLATION_CODES.add("02");	// No data foundDATA_INTEGRITY_VIOLATION_CODES.add("22");	// Value out of rangeDATA_INTEGRITY_VIOLATION_CODES.add("23");	// Integrity constraint violationDATA_INTEGRITY_VIOLATION_CODES.add("27");	// Triggered data change violationDATA_INTEGRITY_VIOLATION_CODES.add("44");	// With check violationDATA_ACCESS_RESOURCE_FAILURE_CODES.add("08");	 // Connection exceptionDATA_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 startedDATA_ACCESS_RESOURCE_FAILURE_CODES.add("58");	 // DB2: unexpected system errorTRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JW");	 // Sybase: internal I/O errorTRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JZ");	 // Sybase: unexpected I/O errorTRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("S1");	 // DB2: communication failureCONCURRENCY_FAILURE_CODES.add("40");	// Transaction rollbackCONCURRENCY_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为开发者开放了多个接口供开发者扩展,实在是用心良苦。




  相关解决方案