Spring JDBC 异常转换器之转换过程分析

时间:2021-01-31 09:17:41
假设调用以下方法
	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路径下:

Spring JDBC 异常转换器之转换过程分析

这个配置文件既是个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为开发者开放了多个接口供开发者扩展,实在是用心良苦。