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的异常转译部分所有分析完毕!
相关文章
相关标签/搜索