javascript
Spring JDBC最佳实践(2)
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
使用DataSourceUtils進(jìn)行Connection的管理由上節(jié)代碼可知,JdbcTemplate在獲取Connection的時(shí)候,并不是直接調(diào)用DataSource的getConnection(),而是調(diào)用了如下的代碼:
Connection con = DataSourceUtils.getConnection(getDataSource());
為什么要這么做呢?
實(shí)際上,如果對于一個(gè)功能帶一的JdbcTemplate來說,調(diào)用如下的代碼就夠了:
Connection con = dataSource.getConnection();
只不過,spring所提供的JdbcTemplate要關(guān)注更多的東西,所以,在從dataSource取得連接的時(shí)候,需要多做一些事情。
org.springframework.jdbc.datasource.DataSourceUtils所提供的方法,用來從指定的DataSource中獲取或者釋放連接,它會(huì)將取得的Connection綁定到當(dāng)前的線程,以便在使用Spring所提供的統(tǒng)一事務(wù)抽象層進(jìn)行事務(wù)管理的時(shí)候使用。
為什么要使用NativeJdbcExtractor
在execute()方法中可以看到:
if (this.nativeJdbcExtractor != null) {stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);}
通過該處理,獲取的將是相應(yīng)的驅(qū)動(dòng)程序所提供的實(shí)現(xiàn)類,而不是相應(yīng)的代理對象。
JdbcTemplate內(nèi)部定義了一個(gè)NativeJdbcExtractor類型的實(shí)例變量:
/** Custom NativeJdbcExtractor */private NativeJdbcExtractor nativeJdbcExtractor;
當(dāng)我們想用驅(qū)動(dòng)對象所提供的原始API的時(shí)候,可以通過JdbcTemplate的如下代碼:
public void setNativeJdbcExtractor(NativeJdbcExtractor extractor) {this.nativeJdbcExtractor = extractor;}這樣將會(huì)獲取真正的目標(biāo)對象而不是代理對象。
spring默認(rèn)提供面向Commons DBCP、C3P0、Weblogic、Websphere等數(shù)據(jù)源的NativeJdbcExtractor的實(shí)現(xiàn)類: CommonsDbcpNativeJdbcExtractor:為Jakarta Commons DBCP數(shù)據(jù)庫連接池所提供的NativeJdbcExtractor實(shí)現(xiàn)類 C3P0NativeJdbcExtractor:為C3P0數(shù)據(jù)庫連接池所提供的NativeJdbcExtractor實(shí)現(xiàn)類 WebLogicNativeJdbcExtractor:為Weblogic所準(zhǔn)備的NativeJdbcExtractor實(shí)現(xiàn)類
WebSphereNativeJdbcExtractor:為WebSphere所準(zhǔn)備的NativeJdbcExtractor實(shí)現(xiàn)類
控制JdbcTemplate的行為 JdbcTemplate在使用Statement或者PreparedStatement等進(jìn)行具體的數(shù)據(jù)操作之前,會(huì)調(diào)用如下的代碼:
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());}這樣便可以設(shè)置Statement每次抓取的行數(shù) 等等。
SQLException到DataAccessException的轉(zhuǎn)譯 因?yàn)镴dbcTemplate直接操作的是JDBC API,所以它需要捕獲在此期間可能發(fā)生的SQLException,處理的宗旨是將SQLException 轉(zhuǎn)譯到spring的數(shù)據(jù)訪問異常層次體系,以統(tǒng)一數(shù)據(jù)訪問異常的處理方式,這個(gè)工作主要是交給了SQLExceptionTranslator,該 接口的定義如下:
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);}
該接口有兩個(gè)主要的實(shí)現(xiàn)類,SQLErrorCodeSQLExceptionTranslator和SQLStateSQLExceptionTranslator,如下所示:
SQLExceptionSubclassTranslator是Spring2.5新加的實(shí)現(xiàn)類,主要用于JDK6發(fā)布的將JDBC4版本中新定義的異常體系轉(zhuǎn)化為spring的異常體系,對于之前的版本,該類派不上用場。
SQLErrorCodeSQLExceptionTranslator會(huì)基于SQLExcpetion所返回的ErrorCode進(jìn)行異常轉(zhuǎn)譯。通常情況下,根據(jù)各個(gè)數(shù)據(jù)庫提供商所提供的ErrorCode進(jìn)行分析要比基于SqlState的方式要準(zhǔn)確的多。默認(rèn)情況下,JdbcTemplate會(huì)采用SQLErrorCodeSQLExceptionTranslator進(jìn)行SQLException的轉(zhuǎn)譯,當(dāng)ErrorCode無法提供足夠的信息的時(shí)候,會(huì)轉(zhuǎn)而求助SQLStateSQLExceptionTranslator。
如果JdbcTemplate默認(rèn)的SQLErrorCodeSQLExceptionTranslator無法滿足當(dāng)前異常轉(zhuǎn)譯的需要,我們可以擴(kuò)展SQLErrorCodeSQLExceptionTranslator,使其支持更多的情況,有兩種方法進(jìn)行擴(kuò)展:提供其子類或者在classpath下提供相應(yīng)的配置文件,
我們先大致看一下SQLErrorCodeSQLExceptionTranslator的大致調(diào)用規(guī)則,然后再從代碼層面上研究下,r進(jìn)行轉(zhuǎn)譯的大致的流程如下:
1、SQLErrorCodeSQLExceptionTranslator定義了如下的自定義異常轉(zhuǎn)譯的方法:
程序流程首先會(huì)檢查該自定義轉(zhuǎn)譯的方法是否能夠?qū)Ξ?dāng)前的SQLException進(jìn)行轉(zhuǎn)譯,如果可以,直接返回DataAccessException類型,如果為null,表示無法轉(zhuǎn)譯,程序?qū)?zhí)行下一步,由上面代碼可以看到該方法直接返回null,所以,流程要進(jìn)入下一步。
2、使用org.springframework.jdbc.support.SQLErrorCodesFactory所加載的SQLErrorCodes進(jìn)行異常轉(zhuǎn)譯,其中,SQLErrorCodesFactory加載SQLErrorCodes的流程為:
1>使用org/springframework/jdbc/support/sql-error-codes.xml路徑下記載了各個(gè)數(shù)據(jù)庫提供商的配置文件,提取相應(yīng)的SQLErrorCodes。
2>如果發(fā)現(xiàn)當(dāng)前應(yīng)用的根目錄下存在名稱為sql-error-codes.xml的配置文件,則加載該文件并覆蓋默認(rèn)的ErrorCodes定義。
3、如果基于ErrorCode的異常轉(zhuǎn)譯還是沒法搞定的話,SQLErrorCodeSQLExceptionTranslator只能求助于SQLStateSQLExceptionTranslator或者SQLExceptionSubclassTranslator
下面從代碼層面上剖析之:
假若JdbcTemplate的如下模板方法在執(zhí)行的過程中發(fā)生了異常:
會(huì)執(zhí)行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,所以創(chuàng)建了SQLErrorCodeSQLExceptionTranslator,看下其構(gòu)造方法:
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,備份了一個(gè)SQLExceptionSubclassTranslator類型的Translator,否則備份一個(gè)SQLStateSQLExceptionTranslator
setDataSource(DataSource dataSource)通過SQLErrorCodesFactory創(chuàng)建一個(gè)SQLErrorCodes類型的變量:
public void setDataSource(DataSource dataSource) {this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);}
SQLErrorCodesFactory采用了單例模式,在其構(gòu)造方法中依然利用了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;}
可知首先會(huì)讀取org.springframework.jdbc.support下的sql-error-codes.xml文件,如果classpath下也有該文件,則覆蓋之,
這樣便生成了sqlErrorCodes
getExceptionTranslator().translate("StatementCallback", getSql(action), ex)的方法如下所示:
doTranslate(task, sql, ex)讓子類實(shí)現(xiàn),在這個(gè)例子中即是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方法會(huì)調(diào)用SQLExceptionSubclassTranslator或者SQLStateSQLExceptionTranslator的translate的方法轉(zhuǎn)譯這個(gè)異常。
在SQLErrorCodeSQLExceptionTranslator轉(zhuǎn)譯異常的過程中,我們可以在兩個(gè)地方插入自定義的轉(zhuǎn)譯異常:
1、在customTranslate(String task, String sql, SQLException sqlEx)方法中,通過子類化SQLErrorCodeSQLExceptionTranslator,重寫該方法。
2、在classpath下提供sql-error-codes.xml文件。
下面是使用這兩種方式進(jìn)行自定義轉(zhuǎn)譯的具體實(shí)施情況。
1、擴(kuò)展SQLErrorCodeSQLExceptionTranslator
該方法最直接有效,卻不夠方便,需要子類化并且覆寫它的customTranslate方法,
在這里,假設(shè)當(dāng)數(shù)據(jù)庫返回的錯(cuò)誤代碼為111的時(shí)候,將拋出UnknownUncategorizedDataAccessException類型的異常(或者是其它自定義的DataAccessException)除此之外,返回null以保證其它的異常轉(zhuǎn)譯依然采用超類的邏輯進(jìn)行。
為了能使自定義的轉(zhuǎn)譯其作用,我們需要讓JdbcTemplate使用我們的SimpleSQLErrorCodeSQLExceptinTranslator,而不是默認(rèn)的SQLErrorCodeSQLExceptionTranslator,所以,需要如下代碼所示,將SimpleSQLErrorCodeSQLExceptinTranslator設(shè)置給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);在classpath下放置一個(gè)sql-error-codes.xml文件,格式要與默認(rèn)的文件格式相同。
實(shí)際上,它就是一個(gè)基本的基于DTD的Spring IOC容器的配置文件,只不過class是固定的。該配置文件對每個(gè)數(shù)據(jù)庫類型均提供了一個(gè)org.springframework.jdbc.support.SQLErrorCodes的定義。假若我們有另外一個(gè)數(shù)據(jù)庫AnotherDb,要擴(kuò)展該轉(zhuǎn)譯,我們有兩種方式:
1、
2、設(shè)置customTranslations屬性:
<?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的異常轉(zhuǎn)譯部分全部分析完畢!
轉(zhuǎn)載于:https://my.oschina.net/u/218421/blog/38576
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的Spring JDBC最佳实践(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux命令中正则表达式的运用
- 下一篇: 使用MinGW编译Psycopg2