博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring JDBC最佳实践(2)
阅读量:6719 次
发布时间:2019-06-25

本文共 16172 字,大约阅读时间需要 53 分钟。

hot3.png

使用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 &&					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实现类

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定义了如下的自定义异常转译的方法:

protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {		return null;	}

程序流程首先会检查该自定义转译的方法是否能够对当前的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的如下模板方法在执行的过程中发生了异常:

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转译异常的过程中,我们可以在两个地方插入自定义的转译异常:

1、在customTranslate(String task, String sql, SQLException sqlEx)方法中,通过子类化SQLErrorCodeSQLExceptionTranslator,重写该方法。
2、在classpath下提供sql-error-codes.xml文件。
下面是使用这两种方式进行自定义转译的具体实施情况。
1、扩展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,要扩展该转译,我们有两种方式:

1、

AnotherDB*
001
002
0031,0032
004
0051,0052

2、设置customTranslations属性:
AnotherDB*
111
org.springframework.dao.IncorrectResultSizeDataAccessException

至此,spring的异常转译部分全部分析完毕!

转载于:https://my.oschina.net/u/218421/blog/38576

你可能感兴趣的文章
关于Socket通信服务的心跳包(转)
查看>>
.net面试问答(大汇总)
查看>>
BST B-树 B+树 B*树简介
查看>>
21、ASP.NET MVC入门到精通——ASP.NET MVC4优化
查看>>
Arm-kernel 内存收集【转】
查看>>
HTML5之Canvas标签简要学习
查看>>
du熊学斐波那契I
查看>>
Silverlight1.1架构图
查看>>
js实现获取值传到input里边
查看>>
【原+转】用CMake代替makefile进行跨平台交叉编译
查看>>
swift3.0:CoreData的使用
查看>>
Silverlight实用窍门系列:3.Silverlight鼠标动态绘制矩形【实例源码下载】
查看>>
postmaster.c 中的 ListenAddresses
查看>>
托付和事件的使用
查看>>
关于Java的转义字符
查看>>
测试管理工具QC使用指南--QC用户
查看>>
使用FDO API连接到各种数据源
查看>>
CentOS 6.5下Redis安装记录
查看>>
Activiti-5.3工作流引擎-源码解析(流程文档解析)
查看>>
python3中的迭代器与生成器
查看>>