Spring JDBC最佳實踐(2)

使用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所提供的統一事務抽象層進行事務管理的時候使用。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);
			}

經過該處理,獲取的將是相應的驅動程序所提供的實現類,而不是相應的代理對象。
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實現類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,因此,流程要進入下一步。
二、使用org.springframework.jdbc.support.SQLErrorCodesFactory所加載的SQLErrorCodes進行異常轉譯,其中,SQLErrorCodesFactory加載SQLErrorCodes的流程爲:
1>使用org/springframework/jdbc/support/sql-error-codes.xml路徑下記載了各個數據庫提供商的配置文件,提取相應的SQLErrorCodes。
2>若是發現當前應用的根目錄下存在名稱爲sql-error-codes.xml的配置文件,則加載該文件並覆蓋默認的ErrorCodes定義。

三、若是基於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
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)的方法以下所示:

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以保證其它的異常轉譯依然採用超類的邏輯進行。
爲了能使自定義的轉譯其做用,咱們須要讓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,要擴展該轉譯,咱們有兩種方式:
一、

<?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>


二、設置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的異常轉譯部分所有分析完畢!
相關文章
相關標籤/搜索