Spring JDBC最佳实践(2)

使用DataSourceUtils进行Connection的管理 
由上节代码可知,JdbcTemplate在获取Connection的时候,并不是直接调用DataSource的getConnection(),而是调用了如下的代码: 
1Connection con = DataSourceUtils.getConnection(getDataSource());

为什么要这么做呢? 
实际上,如果对于一个功能带一的JdbcTemplate来说,调用如下的代码就够了: 
1Connection con = dataSource.getConnection();

只不过,spring所提供的JdbcTemplate要关注更多的东西,所以,在从dataSource取得连接的时候,需要多做一些事情。 

org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用来从指定的DataSource中获取或者释放连接,它会将取得的Connection绑定到当前的线程,以便在使用Spring所提供的统一事务抽象层进行事务管理的时候使用。

为什么要使用NativeJdbcExtractor
在execute()方法中可以看到:

1if (this.nativeJdbcExtractor != null &&
2        this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
3    conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
4}

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

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

当我们想用驱动对象所提供的原始API的时候,可以通过JdbcTemplate的如下代码: 
1public 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等进行具体的数据操作之前,会调用如下的代码:

01protected 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,该 接口的定义如下:

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

该接口有两个主要的实现类,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定义了如下的自定义异常转译的方法:

1protected 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的如下模板方法在执行的过程中发生了异常:

01public 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块中的 
1throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);

getExceptionTranslator()如下定义: 
01public 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,看下其构造方法: 
1public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {
2    this();
3    setDataSource(dataSource);
4}

this()代码为: 
1public 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类型的变量:
1public void setDataSource(DataSource dataSource) {
2    this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
3}

SQLErrorCodesFactory采用了单例模式,在其构造方法中依然利用了BeanFactory,传入的文件为xml bean配置文件: 
01protected 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)的方法如下所示:

01public 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,代码如下: 
01protected 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.
12    DataAccessException dex = customTranslate(task, sql, sqlEx);
13    if (dex != null) {
14        return dex;
15    }
16 
17    // Check SQLErrorCodes with corresponding error code, if available.
18    if (this.sqlErrorCodes != null) {
19        String errorCode = null;
20        if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {
21            errorCode = sqlEx.getSQLState();
22        }
23        else {
24            errorCode = Integer.toString(sqlEx.getErrorCode());
25        }
26 
27        if (errorCode != null) {
28            // Look for defined custom translations first.
29            CustomSQLErrorCodesTranslation[] customTranslations =this.sqlErrorCodes.getCustomTranslations();
30            if (customTranslations != null) {
31                for (int i = 0; i < customTranslations.length; i++) {
32                    CustomSQLErrorCodesTranslation customTranslation = customTranslations[i];
33                    if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) {
34                        if (customTranslation.getExceptionClass() != null) {
35                            DataAccessException customException = createCustomException(
36                                    task, sql, sqlEx, customTranslation.getExceptionClass());
37                            if (customException != null) {
38                                logTranslation(task, sql, sqlEx, true);
39                                return customException;
40                            }
41                        }
42                    }
43                }
44            }
45            // Next, look for grouped error codes.
46            if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
47                logTranslation(task, sql, sqlEx, false);
48                return new BadSqlGrammarException(task, sql, sqlEx);
49            }
50            else if(Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
51                logTranslation(task, sql, sqlEx, false);
52                return new InvalidResultSetAccessException(task, sql, sqlEx);
53            }
54            else if(Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
55                logTranslation(task, sql, sqlEx, false);
56                return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
57            }
58            else if(Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >=0) {
59                logTranslation(task, sql, sqlEx, false);
60                return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
61            }
62            else if(Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
63                logTranslation(task, sql, sqlEx, false);
64                return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
65            }
66            else if(Arrays.binarySearch(this.sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
67                logTranslation(task, sql, sqlEx, false);
68                return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
69            }
70            else if(Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >=0) {
71                logTranslation(task, sql, sqlEx, false);
72                return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
73            }
74            else if(Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
75                logTranslation(task, sql, sqlEx, false);
76                return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
77            }
78            else if(Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
79                logTranslation(task, sql, sqlEx, false);
80                return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
81            }
82        }
83    }
84 
85    // We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.
86    if (logger.isDebugEnabled()) {
87        String codes = null;
88        if (this.sqlErrorCodes != null &&this.sqlErrorCodes.isUseSqlStateForTranslation()) {
89            codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();
90        }
91        else {
92            codes = "Error code '" + sqlEx.getErrorCode() + "'";
93        }
94        logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator");
95    }
96 
97    return null;
98}

可知假如该方法返回的是null,translate方法会调用SQLExceptionSubclassTranslator或者SQLStateSQLExceptionTranslator的translate的方法转译这个异常。

在SQLErrorCodeSQLExceptionTranslator转译异常的过程中,我们可以在两个地方插入自定义的转译异常:
1、在customTranslate(String task, String sql, SQLException sqlEx)方法中,通过子类化SQLErrorCodeSQLExceptionTranslator,重写该方法。
2、在classpath下提供sql-error-codes.xml文件。
下面是使用这两种方式进行自定义转译的具体实施情况。
1、扩展SQLErrorCodeSQLExceptionTranslator
该方法最直接有效,却不够方便,需要子类化并且覆写它的customTranslate方法,

01package com.google.spring.jdbc;
02 
03import java.sql.SQLException;
04 
05import org.springframework.dao.DataAccessException;
06import org.springframework.dao.UncategorizedDataAccessException;
07import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
08 
09public class SimpleSQLErrorCodeSQLExceptinTranslator extendsSQLErrorCodeSQLExceptionTranslator
10{
11    @Override
12    protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx)
13    {
14        if(sqlEx.getErrorCode()==111)
15        {
16            StringBuilder builder = new StringBuilder();
17            builder.append("unexpected data access exception raised when executing ");
18            builder.append(task);
19            builder.append(" with SQL>");
20            builder.append(sql);
21            return newUnknownUncategorizedDataAccessException(builder.toString(),sqlEx);
22        }
23        return null;
24    }
25     
26    private class UnknownUncategorizedDataAccessException extendsUncategorizedDataAccessException
27    {
28        public UnknownUncategorizedDataAccessException(String msg, Throwable cause) {
29            super(msg, cause);
30        }
31    }
32}

在这里,假设当数据库返回的错误代码为111的时候,将抛出UnknownUncategorizedDataAccessException类型的异常(或者是其它自定义的DataAccessException)除此之外,返回null以保证其它的异常转译依然采用超类的逻辑进行。 
为了能使自定义的转译其作用,我们需要让JdbcTemplate使用我们的SimpleSQLErrorCodeSQLExceptinTranslator,而不是默认的SQLErrorCodeSQLExceptionTranslator,所以,需要如下代码所示,将SimpleSQLErrorCodeSQLExceptinTranslator设置给JdbcTemplate: 
1ApplicationContext applicationContext = newClassPathXmlApplicationContext("applicationContext2.xml");
2JdbcTemplate jdbc = (JdbcTemplate)applicationContext.getBean("jdbc");
3DataSource dataSource = (DataSource)applicationContext.getBean("dataSource");
4SimpleSQLErrorCodeSQLExceptinTranslator simpleSQLErrorCodeSQLExceptinTranslator =new SimpleSQLErrorCodeSQLExceptinTranslator();
5simpleSQLErrorCodeSQLExceptinTranslator.setDataSource(dataSource);
6jdbc.setExceptionTranslator(simpleSQLErrorCodeSQLExceptinTranslator);
在classpath下放置一个sql-error-codes.xml文件,格式要与默认的文件格式相同。 

实际上,它就是一个基本的基于DTD的Spring IOC容器的配置文件,只不过class是固定的。该配置文件对每个数据库类型均提供了一个org.springframework.jdbc.support.SQLErrorCodes的定义。假若我们有另外一个数据库AnotherDb,要扩展该转译,我们有两种方式:
1、

01<?xml version="1.0" encoding="UTF-8"?>
02<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
03 
04<beans>
05 
06    <bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes">
07        <property name="databaseProductName">
08            <value>AnotherDB*</value>
09        </property>
10        <property name="badSqlGrammarCodes">
11            <value>001</value>
12        </property>
13        <property name="dataIntegrityViolationCodes">
14            <value>002</value>
15        </property>
16        <property name="dataAccessResourceFailureCodes">
17            <value>0031,0032</value>
18        </property>
19        <property name="transientDataAccessResourceCodes">
20            <value>004</value>
21        </property>
22        <property name="deadlockLoserCodes">
23            <value>0051,0052</value>
24        </property>
25    </bean>
26</beans>


2、设置customTranslations属性: 
01<?xml version="1.0" encoding="UTF-8"?>
02<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
03 
04<beans>
05 
06    <bean id="AnotherDB" class="org.springframework.jdbc.support.SQLErrorCodes">
07        <property name="databaseProductName">
08            <value>AnotherDB*</value>
09        </property>
10        <property name="customTranslations">
11            <list>
12               <beanclass="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation">
13                   <property name="errorCodes">111</property>
14                   <property name="exceptionClass">
15                       org.springframework.dao.IncorrectResultSizeDataAccessException
16                   </property>
17               </bean>
18            </list>
19        </property>
20    </bean>
21</beans>

至此,spring的异常转译部分全部分析完毕!
原文地址:https://www.cnblogs.com/chenying99/p/2625931.html