Connection con = DataSourceUtils.getConnection(getDataSource());
为何要这么作呢?
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); }
经过该处理,获取的将是相应的驱动程序所提供的实现类,而不是相应的代理对象。
/** 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,因此,流程要进入下一步。
三、若是基于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
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以保证其它的异常转译依然采用超类的逻辑进行。
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>
<?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的异常转译部分所有分析完毕!