Spring提供了兩種使用JDBC API的最佳實踐,一種是以JdbcTemplate爲核心的基於Template的JDBC的使用方式,另外一種則是在JdbcTemplate基礎之上的構建的基於操做對象的JDBC的使用方式。java
基於Template的JDBC的使用方式
該使用方式的最初設想和原型,須要追溯到Rod Johnson在03年出版的Expert One-on-One J2EE Design and Development,在該書的Practical Data Access(數據訪問實踐)中,Rod針對JDBC使用中的一些問題提出了一套改進的實踐原型,並最終將該原型完善後在Spring框架中發佈。算法
JDBC的尷尬
JDBC做爲Java平臺的訪問關係數據庫的標準,其成功是 有目共睹的。幾乎全部java平臺的數據訪問,都直接或者間接的使用了JDBC,它是整個java平臺面向關係數據庫進行數據訪問的基石。
做爲一個標準,無疑JDBC是很成功的,可是要說JDBC在使用過程中多麼的受人歡迎,則不盡然了。JDBC主要是面向較爲底層的數據庫操做,因此在設計的過程中 ,比較的貼切底層以提供儘量多的功能特點。從這個角度來講,JDBC API的設計無可厚非。但是,過於貼切底層的API的設計,對於開發人員則未必是一件好事。即便執行一個最簡單的查詢,開發人員也要按照API的規矩寫上一大堆雷同的代碼,若是不能合理的封裝使用JDBC API,在項目中使用JDBC訪問數據所出現的問題估計會令人發瘋!
對於一般的項目開發來講,若是層次劃分很明確,數據訪問邏輯通常應該在DAO層中實現。根據功能模塊的劃分,可能每一個開發人員都會分得或多或少的實現相應的DAO的任務,假設開發人員A在分得了DAO實現任務後進行開發,他或許開發了以下所示的代碼:
spring
package com.google.spring.jdbc; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class DaoWithA implements IDao { private final Log logger = LogFactory.getLog(DaoWithA.class); private DataSource dataSource = null; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public int updateSomething(String sql) { int count; Connection conn = null; Statement stmt = null; try { conn = getDataSource().getConnection(); stmt = conn.createStatement(); count = stmt.executeUpdate(sql); stmt.close(); stmt = null; } catch (SQLException e) { throw new RuntimeException(e); } finally { if(stmt!=null) { try { stmt.close(); } catch (SQLException ex) { logger.warn("fail to close statement:"+ex); } } if(conn!=null) { try { conn.close(); } catch (Exception ex) { logger.warn("failed to close Connection:"+ex); } } } return count; } }
而B所負責的DAO的實現中,可能也有相似的更新的操做。無疑,B也要像A這樣,在他的DAO實現類中寫一大堆一樣的JDBC代碼,相似的狀況還可能擴展到C、D等開發人員。若是每一個開發人員都能嚴格的按照JDBC的編程規範開發還好,可是事實是,一個團隊中的開發人員是有差異的。
三、忘記關閉Connection。
JDBC規範在指定數據庫訪問異常的時候也沒有可以進行的很完全:
一、將異常類型定義爲SQLException是一個值得商榷的地方。
二、SQLExcpetion沒有采用將具體的異常狀況子類化,以進一步抽象不一樣的數據訪問的狀況,而是採用ErrorCode的方式來區分訪問過程當中所出現的不一樣異常狀況,其實這也沒什麼,只要能區分出具體的錯誤就行,可是JDBC規範卻把ErrorCode的規範留給了數據庫提供商,這致使了不一樣的數據庫供應商對應了不一樣的ErrorCode,進而應用程序在捕獲到SQLException後,還要看當前用的是什麼數據庫。
針對以上問題,Spring提供了相應的解決方案幫助咱們提升開發效率! sql
爲了解決JDBC API在實際使用中的各類尷尬的局面,spring提出了org.springframework.jdbc.core.JdbcTemplate做爲數據訪問的Helper類。JdbcTemplate是整個spring數據抽象層提供的全部JDBC API最佳實踐的基礎,框架內其它更加方便的Helper類以及更高層次的抽象,所有的構建於JdbcTemplate之上。抓住了JdbcTemplate,就抓住了spring框架JDBC API最佳實踐的核心。
歸納的說,JdbcTemplate主要關注一下兩個事情:
一、封裝全部的基於JDBC的數據訪問的代碼,以統一的格式和規範來使用JDBC API。全部的基於JDBC API的數據訪問所有經過JdbcTemplate,從而避免了容易出錯的數據訪問方式。
二、對SQLException所提供的異常信息在框架內進行統一的轉譯,將基於JDBC的數據訪問異常歸入Spring自身的異常層次之中,統一了數據接口的定義,簡化了客戶端代碼對數據訪問異常的處理。
Spring主要是經過模板方法對基於JDBC的數據訪問代碼進行統一的封裝,因此咱們可先看下模板方法:
模板方法主要是用於對算法的行爲或者邏輯進行封裝,即若是多個類中存在類似的算法邏輯或者行爲邏輯,能夠將這些邏輯提取到模板方法中實現,而後讓相應的子類根據須要實現某些自定義的邏輯。
舉個例子,全部的汽車,不論是寶馬仍是大衆,他們的駕駛流程基本上是固定的。實際上,除了少數的實現細節有所不一樣以外,大部分的流程是相同的,基本上是以下所示的流程說明:
一、點火啓動
二、踩剎車,掛前進的檔位(不一樣的車在這一步會存在差別)
三、放下手動控制器(手剎)
四、踩油門啓動車輛運行
此時,咱們能夠聲明一個模板方法類,將肯定的行爲以模板的形式定義,而將不一樣的行爲留給相應的子類來實現:
數據庫
package com.google.spring.jdbc; public abstract class Vehicle { public final void drive() { startTheEnginee();//啓動 putIntoGear(); //前進 looseHandBrake();//放下手剎 stepOnTheGasAndGo();//踩油門前進 } protected abstract void putIntoGear(); private void startTheEnginee() { } private void looseHandBrake() { } private void stepOnTheGasAndGo() { } }
drive()方法就是咱們的模板方法,它被聲明爲final,表示該類是不能被子類重寫的,車輛的自動擋和手動擋是不一樣的,因此留給了子類去實現:
package com.google.spring.jdbc; public class VehicleAT extends Vehicle { @Override protected void putIntoGear() { //掛前進檔位 } }
package com.google.spring.jdbc; public class VehicleMT extends Vehicle { @Override protected void putIntoGear() { //踩離合器 掛前進檔位 } }
這樣,每一個子類實現特有的邏輯就能夠了。apache
JdbcTemplate的演化
若是回頭看一下最初的使用JDBC API進行數據訪問的代碼。就會發現,無論這些代碼是誰負責的,也無論數據訪問的邏輯如何,除了小部分的差別以外,全部的這些代碼幾乎都是按照同一個流程走下來的,以下:
一、conn=getDataSource().getConnection();
二、stmt=conn.createStatement()或者ps=conn.prepareStatement();
三、stmt.executeUpdate(sql)或者ps.executeUpdate() 或者進行相應的查詢。
四、stmt.close() stmt=null
五、catch處理數據庫訪問異常
六、關閉數據庫鏈接避免鏈接泄露致使系統崩潰
對於多個DAO中充斥着幾乎相同的JDBC API的使用代碼,咱們也能夠採用模板方法,多這些代碼進行重構,避免因我的操做不當所出現的種種問題,咱們要作的,就是將一些公共的行爲提取到模板方法中去,而特有的操做,好比每次執行不一樣的更新,或者對不一樣的查詢結果進行不一樣的處理,則放入具體的子類中,這樣,咱們就有個JdbcTemplate的雛形:
編程
package com.google.spring.jdbc; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import org.springframework.dao.DataAccessException; public abstract class JdbcTemplate { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public final Object execute(String sql) { Connection conn = null; Statement stmt = null; try { conn = this.getDataSource().getConnection(); stmt = conn.createStatement(); Object retValue = this.executeWithStatement(stmt, sql); return retValue; } catch (SQLException e) { throw new RuntimeException(e); } finally { closeStatement(stmt); closeConnection(conn); } } protected abstract Object executeWithStatement(Statement stmt,String sql); private final DataAccessException translateSQLException(SQLException e) { DataAccessException dataAccessException = null; //進行相應的轉譯 return dataAccessException; } private final void closeStatement(Statement stmt) { //關閉Statement } private final void closeConnection(Connection conn) { //關閉Connection } }
這樣處理以後,JDBC代碼的使用有了規範。可是,只使用模板方法還不足以提供方便的Helper類。頂着abstract的帽子,每次使用都要進行相應的子類化,這也太不靠譜了。因此,spring中的JdbcTemplate除了引入了模板方法以外,還引入了相應的Callback,避免了每次都子類化,好比,當引入了StatementCallback接口之後:
package com.google.spring.jdbc; import java.sql.Statement; public interface StatementCallback { public Object doWithStatement(Statement stmt) throws SQLException; }
這樣這個真正的Helper類就存在了,以下所示:
package com.google.spring.jdbc; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import org.springframework.dao.DataAccessException; public class JdbcTemplate { private DataSource dataSource; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public final Object execute(StatementCallback callback) { Connection conn = null; Statement stmt = null; try { conn = this.getDataSource().getConnection(); stmt = conn.createStatement(); Object retValue = callback.doWithStatement(stmt); return retValue; } catch (SQLException e) { throw new RuntimeException(e); } finally { closeStatement(stmt); closeConnection(conn); } } private final DataAccessException translateSQLException(SQLException e) { DataAccessException dataAccessException = null; //進行相應的轉譯 return dataAccessException; } private final void closeStatement(Statement stmt) { //關閉Statement } private final void closeConnection(Connection conn) { //關閉Connection } }
要在相應的DAO中使用該JdbcTemplate,只須要根據狀況提供參數和相應的callback就能夠了,以下所示:
final String sql = "update"; JdbcTemplate jdbcTemplate = new JdbcTemplate(); BasicDataSource dataSource = new BasicDataSource(); jdbcTemplate.setDataSource(dataSource); //對dataSource進行setter操做 StatementCallback callback = new StatementCallback() { public Object doWithStatement(Statement stmt) throws SQLException { return new Integer(stmt.executeUpdate(sql)) ; } }; jdbcTemplate.execute(callback);
這樣,開發人員只須要關注與數據訪問邏輯相關的東西,JDBC底層的細節不須要再考慮了。
SQLExceptionTranslator:JdbcTemplate委託此類進行異常的轉譯。安全
JdbcTemplate中的模板方法可分爲以下的四組:
面向Connection的模板方法:
經過ConnectionCallback接口所公開的Connection進行數據訪問
app
import java.sql.Connection; import java.sql.SQLException; import org.springframework.dao.DataAccessException; public interface ConnectionCallback { Object doInConnection(Connection con) throws SQLException, DataAccessException; }
public Object execute(ConnectionCallback action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource()); try { Connection conToUse = con; if (this.nativeJdbcExtractor != null) { // Extract native JDBC Connection, castable to OracleConnection or the like. conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } else { // Create close-suppressing Connection proxy, also preparing returned Statements. conToUse = createConnectionProxy(con); } return action.doInConnection(conToUse); } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex); } finally { DataSourceUtils.releaseConnection(con, getDataSource()); } }
能夠隨意操做Connection。
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()); } }
其它模板方法會根據自身的簽名,構建相應的StatementCallback實例以調用回調接口中公開的方法,例如:
public void execute(final String sql) throws DataAccessException { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } class ExecuteStatementCallback implements StatementCallback, SqlProvider { public Object doInStatement(Statement stmt) throws SQLException { stmt.execute(sql); return null; } public String getSql() { return sql; } } execute(new ExecuteStatementCallback()); }
同一組內的模板方法,能夠根據使用的方便性進行增長,只要在實現的時候,將相應的條件加以對應,改組的回調接口進行封裝,最終調用當前組的核心模板方法便可。
public void execute(final String sql) throws DataAccessException { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } class ExecuteStatementCallback implements StatementCallback, SqlProvider { public Object doInStatement(Statement stmt) throws SQLException { stmt.execute(sql); return null; } public String getSql() { return sql; } } execute(new ExecuteStatementCallback()); }
根據傳入的靜態SQL語句進行更新,無返回值,使用的是Statement框架
public Object execute(String callString, CallableStatementCallback action) throws DataAccessException:
public Object execute(String callString, CallableStatementCallback action) throws DataAccessException { return execute(new SimpleCallableStatementCreator(callString), action); }
內部調的是
public Object execute(CallableStatementCreator csc, CallableStatementCallback action) throws DataAccessException { Assert.notNull(csc, "CallableStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(csc); logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : "")); } Connection con = DataSourceUtils.getConnection(getDataSource()); CallableStatement cs = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } cs = csc.createCallableStatement(conToUse); applyStatementSettings(cs); CallableStatement csToUse = cs; if (this.nativeJdbcExtractor != null) { csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs); } Object result = action.doInCallableStatement(csToUse); handleWarnings(cs); 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. if (csc instanceof ParameterDisposer) { ((ParameterDisposer) csc).cleanupParameters(); } String sql = getSql(csc); csc = null; JdbcUtils.closeStatement(cs); cs = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex); } finally { if (csc instanceof ParameterDisposer) { ((ParameterDisposer) csc).cleanupParameters(); } JdbcUtils.closeStatement(cs); DataSourceUtils.releaseConnection(con, getDataSource()); } }
JdbcTemplate在此提供了一個內部類SimpleCallableStatementCreator:
private static class SimpleCallableStatementCreator implements CallableStatementCreator, SqlProvider { private final String callString; public SimpleCallableStatementCreator(String callString) { Assert.notNull(callString, "Call string must not be null"); this.callString = callString; } public CallableStatement createCallableStatement(Connection con) throws SQLException { return con.prepareCall(this.callString); } public String getSql() { return this.callString; } }
根據傳入的sql語句建立一CallableStatement
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException { return execute(new SimplePreparedStatementCreator(sql), action); }
可知其內部調用了
public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(psc); logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps); PreparedStatement psToUse = ps; if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } Object result = action.doInPreparedStatement(psToUse); handleWarnings(ps); 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. if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); psc = null; JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
在建立PreparedStatementCreator實現類的時候,JdbcTemplate爲其默認提供了一個SimplePreparedStatementCreator內部靜態類,可根據傳入的SQL語句建立一個PreparedStatement 代碼以下:
private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { private final String sql; public SimplePreparedStatementCreator(String sql) { Assert.notNull(sql, "SQL must not be null"); this.sql = sql; } public PreparedStatement createPreparedStatement(Connection con) throws SQLException { return con.prepareStatement(this.sql); } public String getSql() { return this.sql; } }其它的模仿方法與此相似,能夠舉一反三!