Connection con = DataSourceUtils.getConnection(getDataSource());
爲何要這麼作呢?
Connection con = dataSource.getConnection();
只不過,spring所提供的JdbcTemplate要關注更多的東西,因此,在從dataSource取得鏈接的時候,須要多作一些事情。
org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用來從指定的DataSource中獲取或者釋放鏈接,它會將取得的Connection綁定到當前的線程,以便在使用Spring所提供的統一事務抽象層進行事務管理的時候使用。java
爲何要使用NativeJdbcExtractor
在execute()方法中能夠看到:
spring
if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); }
if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); }
經過該處理,獲取的將是相應的驅動程序所提供的實現類,而不是相應的代理對象。
/** Custom NativeJdbcExtractor */ private NativeJdbcExtractor nativeJdbcExtractor;
當咱們想用驅動對象所提供的原始API的時候,能夠經過JdbcTemplate的以下代碼:
public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) { this.nativeJdbcExtractor = extractor; }這樣將會獲取真正的目標對象而不是代理對象。
spring默認提供面向Commons DBCP、C3P0、Weblogic、Websphere等數據源的NativeJdbcExtractor的實現類: CommonsDbcpNativeJdbcExtractor:爲Jakarta Commons DBCP數據庫鏈接池所提供的NativeJdbcExtractor實現類 C3P0NativeJdbcExtractor:爲C3P0數據庫鏈接池所提供的NativeJdbcExtractor實現類 WebLogicNativeJdbcExtractor:爲Weblogic所準備的NativeJdbcExtractor實現類sql
WebSphereNativeJdbcExtractor:爲WebSphere所準備的NativeJdbcExtractor實現類數據庫
控制JdbcTemplate的行爲 JdbcTemplate在使用Statement或者PreparedStatement等進行具體的數據操做以前,會調用以下的代碼:app
protected void applyStatementSettings(Statement stmt) throws SQLException { int fetchSize = getFetchSize(); if (fetchSize > 0) { stmt.setFetchSize(fetchSize); } int maxRows = getMaxRows(); if (maxRows > 0) { stmt.setMaxRows(maxRows); } DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); }
這樣即可以設置Statement每次抓取的行數 等等。ide
SQLException到DataAccessException的轉譯 由於JdbcTemplate直接操做的是JDBC API,因此它須要捕獲在此期間可能發生的SQLException,處理的宗旨是將SQLException 轉譯到spring的數據訪問異常層次體系,以統一數據訪問異常的處理方式,這個工做主要是交給了SQLExceptionTranslator,該 接口的定義以下:fetch
package org.springframework.jdbc.support; import java.sql.SQLException; import org.springframework.dao.DataAccessException; /** * * @author Rod Johnson * @author Juergen Hoeller * @see org.springframework.dao.DataAccessException */ public interface SQLExceptionTranslator { DataAccessException translate(String task, String sql, SQLException ex); }
該接口有兩個主要的實現類,SQLErrorCodeSQLExceptionTranslator和SQLStateSQLExceptionTranslator,以下所示:ui
SQLExceptionSubclassTranslator是Spring2.5新加的實現類,主要用於JDK6發佈的將JDBC4版本中新定義的異常體系轉化爲spring的異常體系,對於以前的版本,該類派不上用場。
SQLErrorCodeSQLExceptionTranslator會基於SQLExcpetion所返回的ErrorCode進行異常轉譯。一般狀況下,根據各個數據庫提供商所提供的ErrorCode進行分析要比基於SqlState的方式要準確的多。默認狀況下,JdbcTemplate會採用SQLErrorCodeSQLExceptionTranslator進行SQLException的轉譯,當ErrorCode沒法提供足夠的信息的時候,會轉而求助SQLStateSQLExceptionTranslator。
若是JdbcTemplate默認的SQLErrorCodeSQLExceptionTranslator沒法知足當前異常轉譯的須要,咱們能夠擴展SQLErrorCodeSQLExceptionTranslator,使其支持更多的狀況,有兩種方法進行擴展:提供其子類或者在classpath下提供相應的配置文件,this
咱們先大體看一下SQLErrorCodeSQLExceptionTranslator的大體調用規則,而後再從代碼層面上研究下,r進行轉譯的大體的流程以下:
一、SQLErrorCodeSQLExceptionTranslator定義了以下的自定義異常轉譯的方法:
google
protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { return null; }
程序流程首先會檢查該自定義轉譯的方法是否可以對當前的SQLException進行轉譯,若是能夠,直接返回DataAccessException類型,若是爲null,表示沒法轉譯,程序將執行下一步,由上面代碼能夠看到該方法直接返回null,因此,流程要進入下一步。
三、若是基於ErrorCode的異常轉譯仍是無法搞定的話,SQLErrorCodeSQLExceptionTranslator只能求助於SQLStateSQLExceptionTranslator或者SQLExceptionSubclassTranslator
下面從代碼層面上剖析之:
倘若JdbcTemplate的以下模板方法在執行的過程當中發生了異常:
public Object execute(StatementCallback action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource()); Statement stmt = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); } Object result = action.doInStatement(stmtToUse); 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()); } }
會執行catch塊中的
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; }
dataSource不爲null,因此建立了SQLErrorCodeSQLExceptionTranslator,看下其構造方法:
public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) { this(); setDataSource(dataSource); }
this()代碼爲:
public SQLErrorCodeSQLExceptionTranslator() { if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) { setFallbackTranslator(new SQLExceptionSubclassTranslator()); } else { setFallbackTranslator(new SQLStateSQLExceptionTranslator()); } }
若是JDK版本大於或等於6,備份了一個SQLExceptionSubclassTranslator類型的Translator,不然備份一個SQLStateSQLExceptionTranslator
public void setDataSource(DataSource dataSource) { this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource); }
SQLErrorCodesFactory採用了單例模式,在其構造方法中依然利用了BeanFactory,傳入的文件爲xml bean配置文件:
protected SQLErrorCodesFactory() { Map errorCodes = null; try { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); 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.EMPTY_MAP; } this.errorCodesMap = errorCodes; }
可知首先會讀取org.springframework.jdbc.support下的sql-error-codes.xml文件,若是classpath下也有該文件,則覆蓋之,
這樣便生成了sqlErrorCodes
getExceptionTranslator().translate("StatementCallback", getSql(action), ex)的方法以下所示:
public DataAccessException translate(String task, 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); }
doTranslate(task, sql, ex)讓子類實現,在這個例子中便是SQLErrorCodeSQLExceptionTranslator,代碼以下:
protected DataAccessException doTranslate(String task, String sql, SQLException ex) { 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; } } // First, try custom translation from overridden method. DataAccessException dex = customTranslate(task, sql, sqlEx); if (dex != null) { return dex; } // Check SQLErrorCodes with corresponding error code, if available. if (this.sqlErrorCodes != null) { String errorCode = null; if (this.sqlErrorCodes.isUseSqlStateForTranslation()) { errorCode = sqlEx.getSQLState(); } else { errorCode = Integer.toString(sqlEx.getErrorCode()); } if (errorCode != null) { // Look for defined custom translations first. CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations(); if (customTranslations != null) { for (int i = 0; i < customTranslations.length; i++) { CustomSQLErrorCodesTranslation customTranslation = customTranslations[i]; 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; } } } } } // 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.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); } } } // We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator. if (logger.isDebugEnabled()) { String codes = null; if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) { codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode(); } else { codes = "Error code '" + sqlEx.getErrorCode() + "'"; } logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator"); } return null; }
可知假如該方法返回的是null,translate方法會調用SQLExceptionSubclassTranslator或者SQLStateSQLExceptionTranslator的translate的方法轉譯這個異常。
在SQLErrorCodeSQLExceptionTranslator轉譯異常的過程當中,咱們能夠在兩個地方插入自定義的轉譯異常:
一、在customTranslate(String task, String sql, SQLException sqlEx)方法中,經過子類化SQLErrorCodeSQLExceptionTranslator,重寫該方法。
二、在classpath下提供sql-error-codes.xml文件。
下面是使用這兩種方式進行自定義轉譯的具體實施狀況。
一、擴展SQLErrorCodeSQLExceptionTranslator
該方法最直接有效,卻不夠方便,須要子類化而且覆寫它的customTranslate方法,
package com.google.spring.jdbc; import java.sql.SQLException; import org.springframework.dao.DataAccessException; import org.springframework.dao.UncategorizedDataAccessException; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; public class SimpleSQLErrorCodeSQLExceptinTranslator extends SQLErrorCodeSQLExceptionTranslator { @Override protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { if(sqlEx.getErrorCode()==111) { StringBuilder builder = new StringBuilder(); builder.append("unexpected data access exception raised when executing "); builder.append(task); builder.append(" with SQL>"); builder.append(sql); return new UnknownUncategorizedDataAccessException(builder.toString(),sqlEx); } return null; } private class UnknownUncategorizedDataAccessException extends UncategorizedDataAccessException { public UnknownUncategorizedDataAccessException(String msg, Throwable cause) { super(msg, cause); } } }
在這裏,假設當數據庫返回的錯誤代碼爲111的時候,將拋出UnknownUncategorizedDataAccessException類型的異常(或者是其它自定義的DataAccessException)除此以外,返回null以保證其它的異常轉譯依然採用超類的邏輯進行。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml"); JdbcTemplate jdbc = (JdbcTemplate)applicationContext.getBean("jdbc"); DataSource dataSource = (DataSource)applicationContext.getBean("dataSource"); SimpleSQLErrorCodeSQLExceptinTranslator simpleSQLErrorCodeSQLExceptinTranslator = new SimpleSQLErrorCodeSQLExceptinTranslator(); simpleSQLErrorCodeSQLExceptinTranslator.setDataSource(dataSource); jdbc.setExceptionTranslator(simpleSQLErrorCodeSQLExceptinTranslator);在classpath下放置一個sql-error-codes.xml文件,格式要與默認的文件格式相同。
實際上,它就是一個基本的基於DTD的Spring IOC容器的配置文件,只不過class是固定的。該配置文件對每一個數據庫類型均提供了一個org.springframework.jdbc.support.SQLErrorCodes的定義。倘若咱們有另一個數據庫AnotherDb,要擴展該轉譯,咱們有兩種方式:
一、
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes"> <property name="databaseProductName"> <value>AnotherDB*</value> </property> <property name="badSqlGrammarCodes"> <value>001</value> </property> <property name="dataIntegrityViolationCodes"> <value>002</value> </property> <property name="dataAccessResourceFailureCodes"> <value>0031,0032</value> </property> <property name="transientDataAccessResourceCodes"> <value>004</value> </property> <property name="deadlockLoserCodes"> <value>0051,0052</value> </property> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes"> <property name="databaseProductName"> <value>AnotherDB*</value> </property> <property name="customTranslations"> <list> <bean class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation"> <property name="errorCodes">111</property> <property name="exceptionClass"> org.springframework.dao.IncorrectResultSizeDataAccessException </property> </bean> </list> </property> </bean> </beans>至此,spring的異常轉譯部分所有分析完畢!