使用DataSourceUtils进行Connection的管理
由上节代码可知,JdbcTemplate在获取Connection的时候,并不是直接调用DataSource的getConnection(),而是调用了如下的代码:
1 |
Connection con = DataSourceUtils.getConnection(getDataSource()); |
为什么要这么做呢?
实际上,如果对于一个功能带一的JdbcTemplate来说,调用如下的代码就够了:
1 |
Connection con = dataSource.getConnection(); |
只不过,spring所提供的JdbcTemplate要关注更多的东西,所以,在从dataSource取得连接的时候,需要多做一些事情。
org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用来从指定的DataSource中获取或者释放连接,它会将取得的Connection绑定到当前的线程,以便在使用Spring所提供的统一事务抽象层进行事务管理的时候使用。
为什么要使用NativeJdbcExtractor
在execute()方法中可以看到:
1 |
if ( this .nativeJdbcExtractor != null && |
2 |
this .nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { |
3 |
conToUse = this .nativeJdbcExtractor.getNativeConnection(con); |
1 |
if ( this .nativeJdbcExtractor != null ) { |
2 |
stmtToUse = this .nativeJdbcExtractor.getNativeStatement(stmt); |
通过该处理,获取的将是相应的驱动程序所提供的实现类,而不是相应的代理对象。
JdbcTemplate内部定义了一个NativeJdbcExtractor类型的实例变量:
1 |
/** Custom NativeJdbcExtractor */ |
2 |
private NativeJdbcExtractor nativeJdbcExtractor; |
当我们想用驱动对象所提供的原始API的时候,可以通过JdbcTemplate的如下代码:
1 |
public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) { |
2 |
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等进行具体的数据操作之前,会调用如下的代码:
01 |
protected void applyStatementSettings(Statement stmt) throws SQLException { |
02 |
int fetchSize = getFetchSize(); |
04 |
stmt.setFetchSize(fetchSize); |
06 |
int maxRows = getMaxRows(); |
08 |
stmt.setMaxRows(maxRows); |
10 |
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); |
这样便可以设置Statement每次抓取的行数 等等。
SQLException到DataAccessException的转译 因为JdbcTemplate直接操作的是JDBC API,所以它需要捕获在此期间可能发生的SQLException,处理的宗旨是将SQLException 转译到spring的数据访问异常层次体系,以统一数据访问异常的处理方式,这个工作主要是交给了SQLExceptionTranslator,该 接口的定义如下:
01 |
package org.springframework.jdbc.support; |
03 |
import java.sql.SQLException; |
05 |
import org.springframework.dao.DataAccessException; |
11 |
* @author Juergen Hoeller |
12 |
* @see org.springframework.dao.DataAccessException |
14 |
public interface SQLExceptionTranslator { |
17 |
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定义了如下的自定义异常转译的方法:
1 |
protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { |
程序流程首先会检查该自定义转译的方法是否能够对当前的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的如下模板方法在执行的过程中发生了异常:
01 |
public Object execute(StatementCallback action) throws DataAccessException { |
02 |
Assert.notNull(action, "Callback object must not be null" ); |
04 |
Connection con = DataSourceUtils.getConnection(getDataSource()); |
05 |
Statement stmt = null ; |
07 |
Connection conToUse = con; |
08 |
if ( this .nativeJdbcExtractor != null && |
09 |
this .nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) { |
10 |
conToUse = this .nativeJdbcExtractor.getNativeConnection(con); |
12 |
stmt = conToUse.createStatement(); |
13 |
applyStatementSettings(stmt); |
14 |
Statement stmtToUse = stmt; |
15 |
if ( this .nativeJdbcExtractor != null ) { |
16 |
stmtToUse = this .nativeJdbcExtractor.getNativeStatement(stmt); |
18 |
Object result = action.doInStatement(stmtToUse); |
22 |
catch (SQLException ex) { |
23 |
// Release Connection early, to avoid potential connection pool deadlock |
24 |
// in the case when the exception translator hasn't been initialized yet. |
25 |
JdbcUtils.closeStatement(stmt); |
27 |
DataSourceUtils.releaseConnection(con, getDataSource()); |
29 |
throw getExceptionTranslator().translate( "StatementCallback" , getSql(action), ex); |
32 |
JdbcUtils.closeStatement(stmt); |
33 |
DataSourceUtils.releaseConnection(con, getDataSource()); |
会执行catch块中的
1 |
throw getExceptionTranslator().translate( "StatementCallback" , getSql(action), ex); |
getExceptionTranslator()如下定义:
01 |
public synchronized SQLExceptionTranslator getExceptionTranslator() { |
02 |
if ( this .exceptionTranslator == null ) { |
03 |
DataSource dataSource = getDataSource(); |
04 |
if (dataSource != null ) { |
05 |
this .exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); |
08 |
this .exceptionTranslator = new SQLStateSQLExceptionTranslator(); |
11 |
return this .exceptionTranslator; |
dataSource不为null,所以创建了SQLErrorCodeSQLExceptionTranslator,看下其构造方法:
1 |
public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) { |
3 |
setDataSource(dataSource); |
this()代码为:
1 |
public SQLErrorCodeSQLExceptionTranslator() { |
2 |
if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) { |
3 |
setFallbackTranslator( new SQLExceptionSubclassTranslator()); |
6 |
setFallbackTranslator( new SQLStateSQLExceptionTranslator()); |
如果JDK版本大于或等于6,备份了一个SQLExceptionSubclassTranslator类型的Translator,否则备份一个SQLStateSQLExceptionTranslator
setDataSource(DataSource dataSource)通过SQLErrorCodesFactory创建一个SQLErrorCodes类型的变量:
1 |
public void setDataSource(DataSource dataSource) { |
2 |
this .sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource); |
SQLErrorCodesFactory采用了单例模式,在其构造方法中依然利用了BeanFactory,传入的文件为xml bean配置文件:
01 |
protected SQLErrorCodesFactory() { |
02 |
Map errorCodes = null ; |
05 |
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); |
06 |
XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf); |
08 |
// Load default SQL error codes. |
09 |
Resource resource = loadResource(SQL_ERROR_CODE_DEFAULT_PATH); |
10 |
if (resource != null && resource.exists()) { |
11 |
bdr.loadBeanDefinitions(resource); |
14 |
logger.warn( "Default sql-error-codes.xml not found (should be included in spring.jar)" ); |
17 |
// Load custom SQL error codes, overriding defaults. |
18 |
resource = loadResource(SQL_ERROR_CODE_OVERRIDE_PATH); |
19 |
if (resource != null && resource.exists()) { |
20 |
bdr.loadBeanDefinitions(resource); |
21 |
logger.info( "Found custom sql-error-codes.xml file at the root of the classpath" ); |
24 |
// Check all beans of type SQLErrorCodes. |
25 |
errorCodes = lbf.getBeansOfType(SQLErrorCodes. class , true , false ); |
26 |
if (logger.isInfoEnabled()) { |
27 |
logger.info( "SQLErrorCodes loaded: " + errorCodes.keySet()); |
30 |
catch (BeansException ex) { |
31 |
logger.warn( "Error loading SQL error codes from config file" , ex); |
32 |
errorCodes = Collections.EMPTY_MAP; |
35 |
this .errorCodesMap = errorCodes; |
可知首先会读取org.springframework.jdbc.support下的sql-error-codes.xml文件,如果classpath下也有该文件,则覆盖之,
这样便生成了sqlErrorCodes
getExceptionTranslator().translate("StatementCallback", getSql(action), ex)的方法如下所示:
01 |
public DataAccessException translate(String task, String sql, SQLException ex) { |
02 |
Assert.notNull(ex, "Cannot translate a null SQLException" ); |
10 |
DataAccessException dex = doTranslate(task, sql, ex); |
12 |
// Specific exception match found. |
15 |
// Looking for a fallback... |
16 |
SQLExceptionTranslator fallback = getFallbackTranslator(); |
17 |
if (fallback != null ) { |
18 |
return fallback.translate(task, sql, ex); |
20 |
// We couldn't identify it more precisely. |
21 |
return new UncategorizedSQLException(task, sql, ex); |
doTranslate(task, sql, ex)让子类实现,在这个例子中即是SQLErrorCodeSQLExceptionTranslator,代码如下:
01 |
protected DataAccessException doTranslate(String task, String sql, SQLException ex) { |
02 |
SQLException sqlEx = ex; |
03 |
if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null ) { |
04 |
SQLException nestedSqlEx = sqlEx.getNextException(); |
05 |
if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null ) { |
06 |
logger.debug( "Using nested SQLException from the BatchUpdateException" ); |
11 |
// First, try custom translation from overridden method. |