Spring JDBC Best Practices (2)

Original Address: https://my.oschina.net/u/218421/blog/38576

Use DataSourceUtils carried Connection management
From the above section of code, JdbcTemplate Connection at the time of acquisition, not directly call the getConnection DataSource (), it calls the following code:

Connection con = DataSourceUtils.getConnection(getDataSource());

Why do you do that?
In fact, if for a function with one JdbcTemplate, it is enough to call the following code:

Connection con = dataSource.getConnection();

But, spring provided JdbcTemplate to pay attention to more things, so, at the time made the connection from the dataSource, need to do something.

Org.springframework.jdbc.datasource.DataSourceUtils method provided for obtaining from the specified DataSource or release the connection, Connection it will be made bound to the current thread, in order to use a unified transaction abstraction layer provided by Spring time transaction management use.

Why use NativeJdbcExtractor
can be seen in the execute () method:

if (this.nativeJdbcExtractor != null &&
                    this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }
if (this.nativeJdbcExtractor != null) {
                stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
            }

By this processing, will achieve the corresponding class driver provided instead of the corresponding proxy object.
Internal JdbcTemplate NativeJdbcExtractor defines a type of instance variables:

/** Custom NativeJdbcExtractor */
    private NativeJdbcExtractor nativeJdbcExtractor;

When we want to use the original driver API provided by the object, you can JdbcTemplate the following code:

public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {
        this.nativeJdbcExtractor = extractor;
    }

This will get the real target object rather than the proxy object.

spring provided by default for Commons DBCP, C3P0, Weblogic, Websphere other data sources NativeJdbcExtractor implementation classes: CommonsDbcpNativeJdbcExtractor: NativeJdbcExtractor of Jakarta Commons DBCP database connection pool implementation provided by class C3P0NativeJdbcExtractor: to C3P0 database connection pool provided NativeJdbcExtractor implementation class WebLogicNativeJdbcExtractor : is prepared NativeJdbcExtractor Weblogic implementation class

WebSphereNativeJdbcExtractor: WebSphere prepared for the implementation class NativeJdbcExtractor

JdbcTemplate behavior control JdbcTemplate Statement prior to use or the like PreparedStatement specific data manipulation, calls the following code:

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());
    }

And so on so that you can set the number of lines per Fetch Statement.

SQLException to DataAccessException translation because JdbcTemplate direct manipulation is JDBC API, so it needs to capture SQLException during this period may occur, the purpose of dealing with the SQLException translation to spring data access exception hierarchy, unified data access exceptions are handled this work is mainly handed over SQLExceptionTranslator, the interface is defined as follows:

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);

}

The interface has two main implementation class, SQLErrorCodeSQLExceptionTranslator and SQLStateSQLExceptionTranslator, as follows:

 

 

SQLExceptionSubclassTranslator is Spring2.5 added new implementation class, mainly for the release of the abnormal JDK6 system JDBC4 version of the new definition of abnormal system into spring, for the previous version, the class of no use.
SQLErrorCodeSQLExceptionTranslator will be returned abnormal translation ErrorCode SQLExcpetion based. Under normal circumstances, the analysis than on SqlState way to more accurately according to ErrorCode each database providers. By default, JdbcTemplate will be using SQLErrorCodeSQLExceptionTranslator SQLException translation, when ErrorCode not provide enough information when the turn will help SQLStateSQLExceptionTranslator.
If JdbcTemplate default SQLErrorCodeSQLExceptionTranslator unable to meet the needs of the current exception translation, we can expand SQLErrorCodeSQLExceptionTranslator, to support more cases, there are two methods for extensions: provide a subclass or provide appropriate configuration file in the classpath,

Let's look briefly at roughly SQLErrorCodeSQLExceptionTranslator call rules, and then the next, r translates roughly follows the flow of research from the code level:
1, SQLErrorCodeSQLExceptionTranslator defines a method to customize the following exceptions translation:

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

Whether the program flow will first check that the custom translation can be translated to the current method of SQLException, if possible, directly DataAccessException return type, if it is null, said it could not translated, the program will be the next step, the code can be seen by the above method direct return null, so the process to the next step.
2, the loaded using SQLErrorCodes org.springframework.jdbc.support.SQLErrorCodesFactory translation abnormality, wherein, the SQLErrorCodesFactory SQLErrorCodes loading process is:
1> describes the use org / springframework / jdbc / support / sql-error-codes.xml path various database provider configuration files, extract the appropriate SQLErrorCodes.
2> if the name found in the root directory of the current application sql-error-codes.xml configuration file, the file is loaded and overrides the default ErrorCodes defined.

3, exception-based translation ErrorCode if they could not get the words, SQLErrorCodeSQLExceptionTranslator can only resort to SQLStateSQLExceptionTranslator or SQLExceptionSubclassTranslator

The following analysis from the code level:
If the following template method JdbcTemplate occurred during the execution of an exception:

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());
        }
    }

Executes catch block

throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);

getExceptionTranslator () is defined as follows:

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 not so creating a SQLErrorCodeSQLExceptionTranslator, look its constructor is null,:

public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {
        this();
        setDataSource(dataSource);
    }

this () code:

public SQLErrorCodeSQLExceptionTranslator() {
        if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) {
            setFallbackTranslator(new SQLExceptionSubclassTranslator());
        }
        else {
            setFallbackTranslator(new SQLStateSQLExceptionTranslator());
        }
    }

If the JDK version is greater than or equal to 6, backed up a SQLExceptionSubclassTranslator type Translator, otherwise a backup SQLStateSQLExceptionTranslator
setDataSource (the DataSource dataSource) to create a variable of type SQLErrorCodes by SQLErrorCodesFactory:

public void setDataSource(DataSource dataSource) {
        this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
    }

SQLErrorCodesFactory embodiment using a single mode, in which the construction method using the BeanFactory still, the incoming file xml bean profile:

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;
    }

First seen that reads sql-error-codes.xml files under org.springframework.jdbc.support, there is also the case if the classpath file, overwriting it,
this will generate a SQLErrorCodes
getExceptionTranslator (). Translate ( "StatementCallback", getSql (action), ex) method as follows:

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) allow subclasses to achieve, in this case The SQLErrorCodeSQLExceptionTranslator that is, code is as follows:

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;
    }

Seen if this method returns null, translate method calls the method SQLExceptionSubclassTranslator or SQLStateSQLExceptionTranslator translate the translation of this exception.

During SQLErrorCodeSQLExceptionTranslator translation exception, we can insert custom translation exception in two places:
1, in customTranslate (String task, String sql, SQLException sqlEx) method, by subclassing SQLErrorCodeSQLExceptionTranslator, override this method.
2, there is provided sql-error-codes.xml file in the classpath.
Here are two ways to customize specific implementation translation.
1, extended SQLErrorCodeSQLExceptionTranslator
the most direct and effective method, but not easy, to be subclassed and overrides its customTranslate method,

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);
        }
    }
}

Here, it is assumed when an error code is returned when the database 111, the type of exception thrown UnknownUncategorizedDataAccessException (or other customized the DataAccessException) In addition, to ensure the return null other anomalies still using translation logic superclass get on.
In order to make custom translation of their role, we need to use our SimpleSQLErrorCodeSQLExceptinTranslator JdbcTemplate, instead of the default SQLErrorCodeSQLExceptionTranslator, therefore, requires the following code shows, the SimpleSQLErrorCodeSQLExceptinTranslator set to 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);

Placing a sql-error-codes.xml file in the classpath, the default format to the same format.

In fact, it is a basic DTD based Spring IOC container configuration file, but class is fixed. The configuration file for each database type are provided in the definition of a org.springframework.jdbc.support.SQLErrorCodes. If we have another database AnotherDb, to expand the translation, we have two ways:
1,

<?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>

2, is provided customTranslations properties:

<?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>

So far, the abnormal part of spring translation of all the analysis is complete!

 

Guess you like

Origin www.cnblogs.com/dyh004/p/11550699.html