原文:https://blog.csdn.net/songjinbin/article/details/19857567
在Spring中,JdbcTemplate是經常被使用的類來幫助用戶程序操作數據庫,在JdbcTemplate為用戶程序提供了許多便利的數據庫操作方法,比如查詢,更新等,而且在Spring中,有許多類似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看來這是Rod.Johnson的慣用手法,
? ??所謂模板板式,就是在父類中定義算法的主要流程,而把一些個性化的步驟延遲到子類中去實現,父類始終控制著整個流程的主動權,子類只是輔助父類實現某些可定制的步驟。?
我們用代碼來說話吧:? 首先,父類要是個抽象類:?
Java代碼??
public abstract class TemplatePattern { //模板方法 public final void templateMethod(){ method1(); method2();//勾子方法 method3();//抽象方法 } private void method1(){ System.out.println("父類實現業務邏輯"); } public void method2(){ System.out.println("父類默認實現,子類可覆蓋"); } protected abstract void method3();//子類負責實現業務邏輯
}
?
父類中有三個方法,分別是method1(),method2()和method3()。? method1()是私有方法,有且只能由父類實現邏輯,由于方法是private的,所以只能父類調用。? method2()是所謂的勾子方法。父類提供默認實現,如果子類覺得有必要定制,則可以覆蓋父類的默認實現。? method3()是子類必須實現的方法,即制定的步驟。? 由此可看出,算法的流程執行順序是由父類掌控的,子類只能配合。?
下面我們來寫第一個子類:? Java代碼??
public class TemplatePatternImpl extends TemplatePattern { @Override protected void method3() { System.out.println("method3()在子類TemplatePatternImpl中實現了!!"); } }
?
這個子類只覆蓋了必須覆蓋的方法,我們來測試一下:? Java代碼??
TemplatePattern t1 = new TemplatePatternImpl();
t1.templateMethod();
?
在控制臺中我們可以看到:? Java代碼?? 父類實現業務邏輯?? 父類默認實現,子類可覆蓋?? method3()在子類TemplatePatternImpl中實現了!!??
OK,我們來看看勾子方法的使用:? 定義第2個子類,實現勾子方法:? Java代碼??
public class TemplatePatternImpl2 extends TemplatePattern { @Override protected void method3() { System.out.println("method3()在子類TemplatePatternImpl2中實現了!!"); } /* (non-Javadoc) * @see com.jak.pattern.template.example.TemplatePattern#method2() */ @Override public void method2() { System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法!!"); } }
?
來測試一下:? Java代碼??
TemplatePattern t2 = new TemplatePatternImpl2();
t2.templateMethod();
?
我們看控制臺:? Java代碼?? 父類實現業務邏輯?? 子類TemplatePatternImpl2覆蓋了父類的method2()方法!!?? method3()在子類TemplatePatternImpl2中實現了!!??
OK,經典的模板模式回顧完了(大家不要拍磚哦~~~~~~~~~~)?
接下來,我們回到正題,自己模仿spring動手寫一個基于模板模式和回調的jdbcTemplate。?
回顧一下,spring為什么要封裝JDBC API,對外提供jdbcTemplate呢(不要仍雞蛋啊¥·%¥#%)? 話說SUN的JDBC API也算是經典了,曾經在某個年代折服了一批人。但隨著歷史的發展,純粹的JDBC API已經過于底層,而且不易控制,由開發人員直接接觸JDBC API,會造成不可預知的風險。還有,數據連接緩存池的發展,也不可能讓開發人員去手工獲取JDBC了。?
好了,我們來看一段曾經堪稱經典的JDBC API代碼吧:? Java代碼??
public List<User> query() { List<User> userList = new ArrayList<User>(); String sql = "select * from User"; Connection con = null; PreparedStatement pst = null; ResultSet rs = null; try { con = HsqldbUtil.getConnection(); pst = con.prepareStatement(sql); rs = pst.executeQuery(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } } catch (SQLException e) { e.printStackTrace(); }finally{ if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } try { pst.close(); } catch (SQLException e) { e.printStackTrace(); } try { if(!con.isClosed()){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (SQLException e) { e.printStackTrace(); } } return userList;
}
?
上面的代碼要若干年前可能是一段十分經典的,還可能被作為example被推廣。但時過境遷,倘若哪位程序員現在再在自己的程序中出現以上代碼,不是說明該公司的開發框架管理混亂,就說明這位程序員水平太“高”了。? 我們試想,一個簡單的查詢,就要做這么一大堆事情,而且還要處理異常,我們不防來梳理一下:? 1、獲取connection? 2、獲取statement? 3、獲取resultset? 4、遍歷resultset并封裝成集合? 5、依次關閉connection,statement,resultset,而且還要考慮各種異常? 6、.....? 啊~~~~ 我快要暈了,在面向對象編程的年代里,這樣的代碼簡直不能上人容忍。試想,上面我們只是做了一張表的查詢,如果我們要做第2張表,第3張表呢,又是一堆重復的代碼:? 1、獲取connection? 2、獲取statement? 3、獲取resultset? 4、遍歷resultset并封裝成集合? 5、依次關閉connection,statement,resultset,而且還要考慮各種異常? 6、.....?
這時候,使用模板模式的時機到了!!!?
通過觀察我們發現上面步驟中大多數都是重復的,可復用的,只有在遍歷ResultSet并封裝成集合的這一步驟是可定制的,因為每張表都映射不同的java bean。這部分代碼是沒有辦法復用的,只能定制。那就讓我們用一個抽象的父類把它們封裝一下吧:? Java代碼??
public abstract class JdbcTemplate { //template method public final Object execute(String sql) throws SQLException{ Connection con = HsqldbUtil.getConnection(); Statement stmt = null; try { stmt = con.createStatement(); ResultSet rs = stmt.executeQuery(sql); Object result = doInStatement(rs);//abstract method return result; } catch (SQLException ex) { ex.printStackTrace(); throw ex; } finally { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if(!con.isClosed()){ try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } catch (SQLException e) { e.printStackTrace(); } } } //implements in subclass protected abstract Object doInStatement(ResultSet rs);
}
?
在上面這個抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負責實現。? 好,我們來定義一個子類,并繼承上面的父類:? Java代碼??
public class JdbcTemplateUserImpl extends JdbcTemplate { @Override protected Object doInStatement(ResultSet rs) { List<User> userList = new ArrayList<User>(); try { User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } catch (SQLException e) { e.printStackTrace(); return null; } } }
?
由代碼可見,我們在doInStatement()方法中,對ResultSet進行了遍歷,最后并返回。? 有人可能要問:我如何獲取ResultSet 并傳給doInStatement()方法啊??呵呵,問這個問題的大多是新手。因為此方法不是由子類調用的,而是由父類調用,并把ResultSet傳遞給子類的。我們來看一下測試代碼:? Java代碼??
String sql = "select * from User";
JdbcTemplate jt = new JdbcTemplateUserImpl();
List<User> userList = (List<User>) jt.execute(sql);
?
就是這么簡單!!?
文章至此仿佛告一段落,莫急!不防讓我們更深入一些...?
試想,如果我每次用jdbcTemplate時,都要繼承一下上面的父類,是不是有些不方面呢?? 那就讓我們甩掉abstract這頂帽子吧,這時,就該callback(回調)上場了?
所謂回調,就是方法參數中傳遞一個接口,父類在調用此方法時,必須調用方法中傳遞的接口的實現類。?
那我們就來把上面的代碼改造一下,改用回調實現吧:?
首先,我們來定義一個回調接口:? Java代碼??
public interface StatementCallback { Object doInStatement(Statement stmt) throws SQLException;
}
?
這時候,我們就要方法的簽名改一下了:? Java代碼??
private final Object execute(StatementCallback action) throws SQLException
?
里面的獲取數據方式也要做如下修改:? Java代碼??
Object result = action.doInStatement(stmt);//abstract method
?
為了看著順眼,我們來給他封裝一層吧:? Java代碼??
public Object query(StatementCallback stmt) throws SQLException{ return execute(stmt);
}
?
OK,大功告成!? 我們來寫一個測試類Test.java測試一下吧:? 這時候,訪問有兩種方式,一種是內部類的方式,一種是匿名方式。?
先來看看內部類的方式:? Java代碼??
//內部類方式 public Object query(final String sql) throws SQLException { class QueryStatementCallback implements StatementCallback { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } } JdbcTemplate jt = new JdbcTemplate(); return jt.query(new QueryStatementCallback()); }
?
在調用jdbcTemplate.query()方法時,傳一個StatementCallBack()的實例過去,也就是我們的內部類。?
再來看看匿名方式:? Java代碼??
//匿名類方式 public Object query2(final String sql) throws Exception{ JdbcTemplate jt = new JdbcTemplate(); return jt.query(new StatementCallback() { public Object doInStatement(Statement stmt) throws SQLException { ResultSet rs = stmt.executeQuery(sql); List<User> userList = new ArrayList<User>(); User user = null; while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUserName(rs.getString("user_name")); user.setBirth(rs.getDate("birth")); user.setCreateDate(rs.getDate("create_date")); userList.add(user); } return userList; } }); }
?
相比之下,這種方法更為簡潔。? 為什么spring不用傳統的模板方法,而加之以Callback進行配合呢?? 試想,如果父類中有10個抽象方法,而繼承它的所有子類則要將這10個抽象方法全部實現,子類顯得非常臃腫。而有時候某個子類只需要定制父類中的某一個方法該怎么辦呢?這個時候就要用到Callback回調了。?
? ? 最后的源碼為:
package com.jak.pattern.template.callbacktemplate;
import java.sql.SQLException;
import java.sql.Statement;public interface StatementCallback {Object doInStatement(Statement stmt) throws SQLException;
}
public class JdbcTemplate {//template methodprivate final Object execute(StatementCallback action) throws SQLException{ Connection con = HsqldbUtil.getConnection();Statement stmt = null;try {stmt = con.createStatement();Object result = action.doInStatement(stmt);//abstract method return result;}catch (SQLException ex) {ex.printStackTrace();throw ex;}finally {try {stmt.close();} catch (SQLException e) {e.printStackTrace();}try {if(!con.isClosed()){try {con.close();} catch (SQLException e) {e.printStackTrace();}}} catch (SQLException e) {e.printStackTrace();}}
}public Object query(StatementCallback stmt) throws SQLException{return execute(stmt);}
}
?
//調用測試類
public class Test {//內部類方式
public Object query(final String sql) throws SQLException {
class QueryStatementCallback implements StatementCallback {public Object doInStatement(Statement stmt) throws SQLException {ResultSet rs = stmt.executeQuery(sql);List<User> userList = new ArrayList<User>();User user = null;while (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUserName(rs.getString("user_name"));user.setBirth(rs.getDate("birth"));user.setCreateDate(rs.getDate("create_date"));userList.add(user);}return userList;}}JdbcTemplate jt = new JdbcTemplate();return jt.query(new QueryStatementCallback());
}//匿名類方式
public Object query2(final String sql) throws Exception{JdbcTemplate jt = new JdbcTemplate();return jt.query(new StatementCallback() {public Object doInStatement(Statement stmt) throws SQLException {ResultSet rs = stmt.executeQuery(sql);List<User> userList = new ArrayList<User>();User user = null;while (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUserName(rs.getString("user_name"));user.setBirth(rs.getDate("birth"));user.setCreateDate(rs.getDate("create_date"));userList.add(user);}return userList;}
});}
public static void main(String[] args) throws Exception {String sql = "select * from User";Test t = new Test();List<User> userList = (List<User>) t.query(sql);List<User> userList2 = (List<User>) t.query2(sql);System.out.println(userList);System.out.println(userList2);}
}
?
言歸正傳,有了上面的基礎后,我們正式開始閱讀源碼:
下面幾個接口是對變化的部分進行建模
接口:創建PreparedStatement。根據Connection來創建PreparedStatement。 ?
public interface PreparedStatementCreator { PreparedStatement createPreparedStatement (Connection conn) throws SQLException; } 使用方法就是:PreparedStatementCreator psc = new PreparedStatementCreator() { public PreparedStatement createPreparedStatement (Connection conn) throws SQLException { PreparedStatement ps = conn. prepareStatement ( "SELECT seat_id AS id FROM available_seats WHERE " + "performance_id = ? AND price_band_id = ?"); ps.setInt(1, performanceId); ps.setInt(2, seatType); return ps; } };
?
給PreparedStatement設置參數。是對PreparedStatmentCreator的設置ps值的一個補充。
public interface PreparedStatementSetter { void setValues(PreparedStatement ps) throws SQLException;
}
? ? ??
對ResultSet進行處理。還有具體的子類。 ?
public interface RowCallbackHandler { void processRow(ResultSet rs) throws SQLException; }
??? ?? 使用方式:
RowCallbackHandler rch = new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { int seatId = rs.getInt(1) ; list.add(new Integer (seatId) );//典型的inner class的應用,list為外部類的變量。 } };
和上面的RowCallbackHandler類似。 ?
public interface ResultSetExtractor { Object extractData(ResultSet rs) throws SQLException, DataAccessException;
}
? ? ? 下面是JdbcTemplate中提供的模板方法。該方法完成對數據庫的查詢:)。
??? ?? 這個execute()方法非常關鍵。 ?
public Object query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { String sql = getSql(psc); //取得不變的SQL部分。 logger.debug("Executing SQL query" + (sql != null ? " [" + sql + "]" : "")); } return execute(psc, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps);//就是給ps來設置參數用的。ps.setInt(1, 0); } rs = ps.executeQuery();//執行查詢 ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); // ResultSetExtractor從ResultSet中將值取出List。 } finally { //最后的善后工作還是需要做好的:) rs.close(),把ps的相關參數清除掉。 JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
?
看看execute()方法吧。 java 代碼 ?
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"); //取得數據庫的連接 Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //創建PreparedStatement ps = psc.createPreparedStatement(conToUse); applyStatementSettings(ps);//這個方法是設置ps的一些屬性,我平時不用,Spring框架倒是考慮得相當全的說。 PreparedStatement psToUse = ps; if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } //調用Callback來完成PreparedStatement的設值。就是調用上面的doInPreparedStatement來使用ps。 Object result = action.doInPreparedStatement(psToUse); SQLWarning warning = ps.getWarnings(); throwExceptionOnWarningIfNotIgnoringWarnings(warning); return result; } //如果有錯誤的話,那么就開始ps.close(), connection.close(); 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.close();ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); / con = null; throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex); }
? ? ? ? //不管怎么樣,ps.close(),?Connection.close()吧,當然這里是releaseConnection。在我的程序中,Connection只有一個,沒有ConnectionPool,當然不會去close?Connection。一般來講,如果沒有Connection的線程池的話,我們肯定也不會經常的關閉Connection,得到Connection。畢竟這個東西非常耗費資源。??
? ? ??
finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
?
JdbcTemplate完成了負責的操作,客戶只需要調用query()就可以完成查詢操作了。當然,JdbcTemplate會實現很多帶其它參數的方法,以方便你的使用。Template設計模式被發揚廣大了。 DataSourceUtils:這個專門用于管理數據庫Connection的類。
?
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { try { return doGetConnection(dataSource); ~~~~~~ //這個方法很舒服,Spring Framework中到處有這樣的方法。為什么要委派到這個動作方法? } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } }
? ? 這里的doGetConnection就稍微復雜一點了。但是如果沒有事務同步管理器的話,那就比較簡單。
只是在Connection上多了一個ConnecionHolder類用于持有Connection,實現ConnectionPool的一點小功能。 ?
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); ~~~~~//Connection的持有器。通過持有器得到Connection。if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(dataSource.getConnection()); } return conHolder.getConnection(); } // Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource"); Connection con = dataSource.getConnection(); ……return con; } ConnectionHolder:Connection的持有器。通過ConnectionHandler來完成對Connection的操作:) 典型的委派。public class ConnectionHolder extends ResourceHolderSupport { private Connection currentConnection; //當前的Connection private ConnectionHandle connectionHandle; //Connection的處理器,因此可以通過該類完成對connection的管理。 public ConnectionHolder(Connection connection) { this.connectionHandle = new SimpleConnectionHandle(connection); } public ConnectionHolder(ConnectionHandle connectionHandle) { Assert.notNull(connectionHandle, "ConnectionHandle must not be null"); this.connectionHandle = connectionHandle; } public Connection getConnection() { Assert.notNull(this.connectionHandle, "Active Connection is required"); if (this.currentConnection == null) { this.currentConnection = this.connectionHandle.getConnection(); } return this.currentConnection; } public void released() { super.released(); if (this.currentConnection != null) { this.connectionHandle.releaseConnection(this.currentConnection); this.currentConnection = null; } } connectionHandle 的接口太純粹了。但是我覺得這個設計太過于細致了:) public interface ConnectionHandle { /** * Fetch the JDBC Connection that this handle refers to. */ Connection getConnection(); /** * Release the JDBC Connection that this handle refers to. * @param con the JDBC Connection to release */ void releaseConnection(Connection con); }
?
? 最后看一下SimpleConnectionHandle,這個ConnectionHandle的簡單實現類。就只有一個Connection可管理。如果有多個Connection可管理的話,這里就是ConnectionPool了:)
java 代碼 ?
public class SimpleConnectionHandle implements ConnectionHandle { private final Connection connection; /** * Create a new SimpleConnectionHandle for the given Connection. * @param connection the JDBC Connection */ public SimpleConnectionHandle(Connection connection) { Assert.notNull(connection, "Connection must not be null"); this.connection = connection; } /** * Return the specified Connection as-is. */ public Connection getConnection() { return connection; } /** * This implementation is empty, as we're using a standard * Connection handle that does not have to be released. */ public void releaseConnection(Connection con) { } public String toString() { return "SimpleConnectionHandle: " + this.connection; } }
?
總結
以上是生活随笔 為你收集整理的spring源码解读之 JdbcTemplate源码 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。