public T execute(StatementCallback 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 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:
true
03000,42000,42601,42602,42622,42804,42P01
23505
23000,23502,23503,23514
53000,53100,53200,53300
55P03
40001
40P01
接着:
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 BAD_SQL_GRAMMAR_CODES = new HashSet<>(8);
private static final Set DATA_INTEGRITY_VIOLATION_CODES = new HashSet<>(8);
private static final Set DATA_ACCESS_RESOURCE_FAILURE_CODES = new HashSet<>(8);
private static final Set TRANSIENT_DATA_ACCESS_RESOURCE_CODES = new HashSet<>(8);
private static final Set 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为开发者开放了多个接口供开发者扩展,实在是用心良苦。