javascript
Spring JDBC最佳实践(2)
2019獨角獸企業重金招聘Python工程師標準>>>
使用DataSourceUtils進行Connection的管理由上節代碼可知,JdbcTemplate在獲取Connection的時候,并不是直接調用DataSource的getConnection(),而是調用了如下的代碼:
Connection con = DataSourceUtils.getConnection(getDataSource());
為什么要這么做呢?
實際上,如果對于一個功能帶一的JdbcTemplate來說,調用如下的代碼就夠了:
Connection con = dataSource.getConnection();
只不過,spring所提供的JdbcTemplate要關注更多的東西,所以,在從dataSource取得連接的時候,需要多做一些事情。
org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用來從指定的DataSource中獲取或者釋放連接,它會將取得的Connection綁定到當前的線程,以便在使用Spring所提供的統一事務抽象層進行事務管理的時候使用。
為什么要使用NativeJdbcExtractor
在execute()方法中可以看到:
if (this.nativeJdbcExtractor != null) {stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);}
通過該處理,獲取的將是相應的驅動程序所提供的實現類,而不是相應的代理對象。
JdbcTemplate內部定義了一個NativeJdbcExtractor類型的實例變量:
/** 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實現類
WebSphereNativeJdbcExtractor:為WebSphere所準備的NativeJdbcExtractor實現類
控制JdbcTemplate的行為 JdbcTemplate在使用Statement或者PreparedStatement等進行具體的數據操作之前,會調用如下的代碼:
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每次抓取的行數 等等。
SQLException到DataAccessException的轉譯 因為JdbcTemplate直接操作的是JDBC API,所以它需要捕獲在此期間可能發生的SQLException,處理的宗旨是將SQLException 轉譯到spring的數據訪問異常層次體系,以統一數據訪問異常的處理方式,這個工作主要是交給了SQLExceptionTranslator,該 接口的定義如下:
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,如下所示:
SQLExceptionSubclassTranslator是Spring2.5新加的實現類,主要用于JDK6發布的將JDBC4版本中新定義的異常體系轉化為spring的異常體系,對于之前的版本,該類派不上用場。
SQLErrorCodeSQLExceptionTranslator會基于SQLExcpetion所返回的ErrorCode進行異常轉譯。通常情況下,根據各個數據庫提供商所提供的ErrorCode進行分析要比基于SqlState的方式要準確的多。默認情況下,JdbcTemplate會采用SQLErrorCodeSQLExceptionTranslator進行SQLException的轉譯,當ErrorCode無法提供足夠的信息的時候,會轉而求助SQLStateSQLExceptionTranslator。
如果JdbcTemplate默認的SQLErrorCodeSQLExceptionTranslator無法滿足當前異常轉譯的需要,我們可以擴展SQLErrorCodeSQLExceptionTranslator,使其支持更多的情況,有兩種方法進行擴展:提供其子類或者在classpath下提供相應的配置文件,
我們先大致看一下SQLErrorCodeSQLExceptionTranslator的大致調用規則,然后再從代碼層面上研究下,r進行轉譯的大致的流程如下:
1、SQLErrorCodeSQLExceptionTranslator定義了如下的自定義異常轉譯的方法:
程序流程首先會檢查該自定義轉譯的方法是否能夠對當前的SQLException進行轉譯,如果可以,直接返回DataAccessException類型,如果為null,表示無法轉譯,程序將執行下一步,由上面代碼可以看到該方法直接返回null,所以,流程要進入下一步。
2、使用org.springframework.jdbc.support.SQLErrorCodesFactory所加載的SQLErrorCodes進行異常轉譯,其中,SQLErrorCodesFactory加載SQLErrorCodes的流程為:
1>使用org/springframework/jdbc/support/sql-error-codes.xml路徑下記載了各個數據庫提供商的配置文件,提取相應的SQLErrorCodes。
2>如果發現當前應用的根目錄下存在名稱為sql-error-codes.xml的配置文件,則加載該文件并覆蓋默認的ErrorCodes定義。
3、如果基于ErrorCode的異常轉譯還是沒法搞定的話,SQLErrorCodeSQLExceptionTranslator只能求助于SQLStateSQLExceptionTranslator或者SQLExceptionSubclassTranslator
下面從代碼層面上剖析之:
假若JdbcTemplate的如下模板方法在執行的過程中發生了異常:
會執行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
setDataSource(DataSource dataSource)通過SQLErrorCodesFactory創建一個SQLErrorCodes類型的變量:
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)的方法如下所示:
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轉譯異常的過程中,我們可以在兩個地方插入自定義的轉譯異常:
1、在customTranslate(String task, String sql, SQLException sqlEx)方法中,通過子類化SQLErrorCodeSQLExceptionTranslator,重寫該方法。
2、在classpath下提供sql-error-codes.xml文件。
下面是使用這兩種方式進行自定義轉譯的具體實施情況。
1、擴展SQLErrorCodeSQLExceptionTranslator
該方法最直接有效,卻不夠方便,需要子類化并且覆寫它的customTranslate方法,
在這里,假設當數據庫返回的錯誤代碼為111的時候,將拋出UnknownUncategorizedDataAccessException類型的異常(或者是其它自定義的DataAccessException)除此之外,返回null以保證其它的異常轉譯依然采用超類的邏輯進行。
為了能使自定義的轉譯其作用,我們需要讓JdbcTemplate使用我們的SimpleSQLErrorCodeSQLExceptinTranslator,而不是默認的SQLErrorCodeSQLExceptionTranslator,所以,需要如下代碼所示,將SimpleSQLErrorCodeSQLExceptinTranslator設置給JdbcTemplate:
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,要擴展該轉譯,我們有兩種方式:
1、
2、設置customTranslations屬性:
<?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的異常轉譯部分全部分析完畢!
轉載于:https://my.oschina.net/u/218421/blog/38576
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Spring JDBC最佳实践(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux命令中正则表达式的运用
- 下一篇: javascript校验2