Spring JDBC最佳实践(2)

使用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);
4 }

1 if (this.nativeJdbcExtractor != null) {
2                 stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
3             }

通过该处理,获取的将是相应的驱动程序所提供的实现类,而不是相应的代理对象。 
JdbcTemplate内部定义了一个NativeJdbcExtractor类型的实例变量: 
1 /** Custom NativeJdbcExtractor */
2 private NativeJdbcExtractor nativeJdbcExtractor;

当我们想用驱动对象所提供的原始API的时候,可以通过JdbcTemplate的如下代码: 
1 public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
2     this.nativeJdbcExtractor = extractor;
3 }
这样将会获取真正的目标对象而不是代理对象。 

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();
03     if (fetchSize > 0) {
04         stmt.setFetchSize(fetchSize);
05     }
06     int maxRows = getMaxRows();
07     if (maxRows > 0) {
08         stmt.setMaxRows(maxRows);
09     }
10     DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
11 }

这样便可以设置Statement每次抓取的行数 等等。

SQLException到DataAccessException的转译 因为JdbcTemplate直接操作的是JDBC API,所以它需要捕获在此期间可能发生的SQLException,处理的宗旨是将SQLException 转译到spring的数据访问异常层次体系,以统一数据访问异常的处理方式,这个工作主要是交给了SQLExceptionTranslator,该 接口的定义如下:

01 package org.springframework.jdbc.support;
02  
03 import java.sql.SQLException;
04  
05 import org.springframework.dao.DataAccessException;
06  
07 /**
08   
09  *
10  * @author Rod Johnson
11  * @author Juergen Hoeller
12  * @see org.springframework.dao.DataAccessException
13  */
14 public interface SQLExceptionTranslator {
15  
16      
17     DataAccessException translate(String task, String sql, SQLException ex);
18  
19 }

该接口有两个主要的实现类,SQLErrorCodeSQLExceptionTranslator和SQLStateSQLExceptionTranslator,如下所示:Spring JDBC最佳实践(2)_第1张图片

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) {
2     return null;
3 }

程序流程首先会检查该自定义转译的方法是否能够对当前的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");
03  
04         Connection con = DataSourceUtils.getConnection(getDataSource());
05         Statement stmt = null;
06         try {
07             Connection conToUse = con;
08             if (this.nativeJdbcExtractor != null &&
09                     this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
10                 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
11             }
12             stmt = conToUse.createStatement();
13             applyStatementSettings(stmt);
14             Statement stmtToUse = stmt;
15             if (this.nativeJdbcExtractor != null) {
16                 stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
17             }
18             Object result = action.doInStatement(stmtToUse);
19             handleWarnings(stmt);
20             return result;
21         }
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);
26             stmt = null;
27             DataSourceUtils.releaseConnection(con, getDataSource());
28             con = null;
29             throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
30         }
31         finally {
32             JdbcUtils.closeStatement(stmt);
33             DataSourceUtils.releaseConnection(con, getDataSource());
34         }
35     }

会执行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 = newSQLErrorCodeSQLExceptionTranslator(dataSource);
06         }
07         else {
08             this.exceptionTranslator = new SQLStateSQLExceptionTranslator();
09         }
10     }
11     return this.exceptionTranslator;
12 }

dataSource不为null,所以创建了SQLErrorCodeSQLExceptionTranslator,看下其构造方法: 
1 public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {
2     this();
3     setDataSource(dataSource);
4 }

this()代码为: 
1 public SQLErrorCodeSQLExceptionTranslator() {
2     if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) {
3         setFallbackTranslator(new SQLExceptionSubclassTranslator());
4     }
5     else {
6         setFallbackTranslator(new SQLStateSQLExceptionTranslator());
7     }
8 }

如果JDK版本大于或等于6,备份了一个SQLExceptionSubclassTranslator类型的Translator,否则备份一个SQLStateSQLExceptionTranslator 
setDataSource(DataSource dataSource)通过SQLErrorCodesFactory创建一个SQLErrorCodes类型的变量:
1 public void setDataSource(DataSource dataSource) {
2     this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
3 }

SQLErrorCodesFactory采用了单例模式,在其构造方法中依然利用了BeanFactory,传入的文件为xml bean配置文件: 
01 protected SQLErrorCodesFactory() {
02         Map errorCodes = null;
03  
04         try {
05             DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
06             XmlBeanDefinitionReader bdr = new XmlBeanDefinitionReader(lbf);
07  
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);
12             }
13             else {
14                 logger.warn("Default sql-error-codes.xml not found (should be included in spring.jar)");
15             }
16  
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");
22             }
23  
24             // Check all beans of type SQLErrorCodes.
25             errorCodes = lbf.getBeansOfType(SQLErrorCodes.classtruefalse);
26             if (logger.isInfoEnabled()) {
27                 logger.info("SQLErrorCodes loaded: " + errorCodes.keySet());
28             }
29         }
30         catch (BeansException ex) {
31             logger.warn("Error loading SQL error codes from config file", ex);
32             errorCodes = Collections.EMPTY_MAP;
33         }
34  
35         this.errorCodesMap = errorCodes;
36     }


可知首先会读取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");
03     if (task == null) {
04         task = "";
05     }
06     if (sql == null) {
07         sql = "";
08     }
09  
10     DataAccessException dex = doTranslate(task, sql, ex);
11     if (dex != null) {
12         // Specific exception match found.
13         return dex;
14     }
15     // Looking for a fallback...
16     SQLExceptionTranslator fallback = getFallbackTranslator();
17     if (fallback != null) {
18         return fallback.translate(task, sql, ex);
19     }
20     // We couldn't identify it more precisely.
21     return new UncategorizedSQLException(task, sql, ex);
22 }

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");
07             sqlEx = nestedSqlEx;
08         }
09     }
10  
11     // First, try custom translation from overridden method.

你可能感兴趣的:(spring,jdbc)