spring中的模板方法jdbctemplate


轉載:http://www.iteye.com/topic/713770?1306420721

 最近一直在研讀spring源碼和學習設計模式,想把自己的一些領悟與大家分享,前幾天發了幾篇簡單的文章,可能由於文字過於簡單,幾次被評為新手貼,心中滴汗啊  沒辦法,工作太忙,大家都知道,寫篇文章是要很大精力地~~~~~ 

今天恰有時間,把這兩天的學習所得與大家分享,盡量寫得詳細一些,專家饒路走,新手覺得好贊一下(不要拍磚哦~~~~)。 
文章源碼在附件中 

注:本文目的意不在“重復發明輪子”,而是借此文來探討Spring JdbcTemplate的內部實現原理,掌握其運用精妙之處,以便在以后的“造輪運動”中能靈活運用。 

話回正轉,這兩天在讀spring的jdbc模板,對Spring源碼的精妙真是佩服得五體投地,極為經典。 
spring中真是集設計模式之大成,而且用得是爐火純青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包圍的包裝等都無疑使用了模板模式,但spring並不是單純使用了模板方法,而是在此基礎上做了創新,配合callback(回調)一起使用,用得極其靈活。 

OK,為了防止文章再被拍磚,我寫得更詳細點吧,我們首先來回顧一下模板模式: 
所謂模板板式,就是在父類中定義算法的主要流程,而把一些個性化的步驟延遲到子類中去實現,父類始終控制着整個流程的主動權,子類只是輔助父類實現某些可定制的步驟。 

有些抽象??? 
好吧,我們用代碼來說話吧: 
首先,父類要是個抽象類:

 1 public abstract class TemplatePattern {  
 2   
 3     //模板方法  
 4     public final void templateMethod(){  
 5           
 6         method1();  
 7         method2();//勾子方法  
 8         method3();//抽象方法  
 9     }  
10     private void method1(){  
11         System.out.println("父類實現業務邏輯");  
12     }  
13     public void method2(){  
14         System.out.println("父類默認實現,子類可覆蓋");  
15     }  
16     protected abstract void method3();//子類負責實現業務邏輯  
17 }  

父類中有三個方法,分別是method1(),method2()和method3()。 
method1()是私有方法,有且只能由父類實現邏輯,由於方法是private的,所以只能父類調用。 
method2()是所謂的勾子方法。父類提供默認實現,如果子類覺得有必要定制,則可以覆蓋父類的默認實現。 
method3()是子類必須實現的方法,即制定的步驟。 
由此可看出,算法的流程執行順序是由父類掌控的,子類只能配合。 

下面我們來寫第一個子類:

1 public class TemplatePatternImpl extends TemplatePattern {  
2   
3     @Override  
4     protected void method3() {  
5         System.out.println("method3()在子類TemplatePatternImpl中實現了!!");  
6   
7     }  
8   
9 }  

這個子類只覆蓋了必須覆蓋的方法,我們來測試一下:

1 TemplatePattern t1 = new TemplatePatternImpl();  
2 t1.templateMethod();  

在控制台中我們可以看到:

1 父類實現業務邏輯  
2 父類默認實現,子類可覆蓋  
3 method3()在子類TemplatePatternImpl中實現了!!

OK,我們來看看勾子方法的使用: 
定義第2個子類,實現勾子方法:

 1 public class TemplatePatternImpl2 extends TemplatePattern {  
 2   
 3     @Override  
 4     protected void method3() {  
 5         System.out.println("method3()在子類TemplatePatternImpl2中實現了!!");  
 6   
 7     }  
 8   
 9     /* (non-Javadoc) 
10      * @see com.jak.pattern.template.example.TemplatePattern#method2() 
11      */  
12     @Override  
13     public void method2() {  
14         System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法!!");  
15     }  
16       
17 }  
 1 public class TemplatePatternImpl2 extends TemplatePattern {  
 2   
 3     @Override  
 4     protected void method3() {  
 5         System.out.println("method3()在子類TemplatePatternImpl2中實現了!!");  
 6   
 7     }  
 8   
 9     /* (non-Javadoc) 
10      * @see com.jak.pattern.template.example.TemplatePattern#method2() 
11      */  
12     @Override  
13     public void method2() {  
14         System.out.println("子類TemplatePatternImpl2覆蓋了父類的method2()方法!!");  
15     }  
16       
17 }  

來測試一下:

1 TemplatePattern t2 = new TemplatePatternImpl2();  
2 t2.templateMethod();  

我們看控制台:

父類實現業務邏輯  
子類TemplatePatternImpl2覆蓋了父類的method2()方法!!  
method3()在子類TemplatePatternImpl2中實現了!! 

OK,經典的模板模式回顧完了(大家不要拍磚哦~~~~~~~~~~) 

接下來,我們回到正題,自己模仿spring動手寫一個基於模板模式和回調的jdbcTemplate。 

回顧一下,spring為什么要封裝JDBC API,對外提供jdbcTemplate呢(不要仍雞蛋啊¥·%¥#%) 
話說SUN的JDBC API也算是經典了,曾經在某個年代折服了一批人。但隨着歷史的發展,純粹的JDBC API已經過於底層,而且不易控制,由開發人員直接接觸JDBC API,會造成不可預知的風險。還有,數據連接緩存池的發展,也不可能讓開發人員去手工獲取JDBC了。 

好了,我們來看一段曾經堪稱經典的JDBC API代碼吧:

 1 public List<User> query() {  
 2   
 3     List<User> userList = new ArrayList<User>();  
 4     String sql = "select * from User";  
 5   
 6     Connection con = null;  
 7     PreparedStatement pst = null;  
 8     ResultSet rs = null;  
 9     try {  
10         con = HsqldbUtil.getConnection();  
11         pst = con.prepareStatement(sql);  
12         rs = pst.executeQuery();  
13   
14         User user = null;  
15         while (rs.next()) {  
16   
17             user = new User();  
18             user.setId(rs.getInt("id"));  
19             user.setUserName(rs.getString("user_name"));  
20             user.setBirth(rs.getDate("birth"));  
21             user.setCreateDate(rs.getDate("create_date"));  
22             userList.add(user);  
23         }  
24   
25   
26     } catch (SQLException e) {  
27         e.printStackTrace();  
28     }finally{  
29         if(rs != null){  
30             try {  
31                 rs.close();  
32             } catch (SQLException e) {  
33                 e.printStackTrace();  
34             }  
35         }  
36         try {  
37             pst.close();  
38         } catch (SQLException e) {  
39             e.printStackTrace();  
40         }  
41         try {  
42             if(!con.isClosed()){  
43                 try {  
44                     con.close();  
45                 } catch (SQLException e) {  
46                     e.printStackTrace();  
47                 }  
48             }  
49         } catch (SQLException e) {  
50             e.printStackTrace();  
51         }  
52           
53     }  
54     return userList;  
55 }  

上面的代碼要若干年前可能是一段十分經典的,還可能被作為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。這部分代碼是沒有辦法復用的,只能定制。那就讓我們用一個抽象的父類把它們封裝一下吧:

 1 public abstract class JdbcTemplate {  
 2   
 3     //template method  
 4     public final Object execute(String sql) throws SQLException{  
 5           
 6         Connection con = HsqldbUtil.getConnection();  
 7         Statement stmt = null;  
 8         try {  
 9    
10             stmt = con.createStatement();  
11             ResultSet rs = stmt.executeQuery(sql);  
12             Object result = doInStatement(rs);//abstract method   
13             return result;  
14         }  
15         catch (SQLException ex) {  
16              ex.printStackTrace();  
17              throw ex;  
18         }  
19         finally {  
20    
21             try {  
22                 stmt.close();  
23             } catch (SQLException e) {  
24                 e.printStackTrace();  
25             }  
26             try {  
27                 if(!con.isClosed()){  
28                     try {  
29                         con.close();  
30                     } catch (SQLException e) {  
31                         e.printStackTrace();  
32                     }  
33                 }  
34             } catch (SQLException e) {  
35                 e.printStackTrace();  
36             }  
37               
38         }  
39     }  
40       
41     //implements in subclass  
42     protected abstract Object doInStatement(ResultSet rs);  
43 }  

在上面這個抽象類中,封裝了SUN JDBC API的主要流程,而遍歷ResultSet這一步驟則放到抽象方法doInStatement()中,由子類負責實現。 
好,我們來定義一個子類,並繼承上面的父類:

 1 public class JdbcTemplateUserImpl extends JdbcTemplate {  
 2   
 3     @Override  
 4     protected Object doInStatement(ResultSet rs) {  
 5         List<User> userList = new ArrayList<User>();  
 6           
 7         try {  
 8             User user = null;  
 9             while (rs.next()) {  
10   
11                 user = new User();  
12                 user.setId(rs.getInt("id"));  
13                 user.setUserName(rs.getString("user_name"));  
14                 user.setBirth(rs.getDate("birth"));  
15                 user.setCreateDate(rs.getDate("create_date"));  
16                 userList.add(user);  
17             }  
18             return userList;  
19         } catch (SQLException e) {  
20             e.printStackTrace();  
21             return null;  
22         }  
23     }  
24   
25 }  

由代碼可見,我們在doInStatement()方法中,對ResultSet進行了遍歷,最后並返回。 
有人可能要問:我如何獲取ResultSet 並傳給doInStatement()方法啊??呵呵,問這個問題的大多是新手。因為此方法不是由子類調用的,而是由父類調用,並把ResultSet傳遞給子類的。我們來看一下測試代碼:

1 String sql = "select * from User";  
2 JdbcTemplate jt = new JdbcTemplateUserImpl();  
3 List<User> userList = (List<User>) jt.execute(sql);  

就是這么簡單!! 

文章至此仿佛告一段落,莫急!不防讓我們更深入一些... 

試想,如果我每次用jdbcTemplate時,都要繼承一下上面的父類,是不是有些不方面呢? 
那就讓我們甩掉abstract這頂帽子吧,這時,就該callback(回調)上場了 


所謂回調,就是方法參數中傳遞一個接口,父類在調用此方法時,必須調用方法中傳遞的接口的實現類。 

那我們就來把上面的代碼改造一下,改用回調實現吧: 

首先,我們來定義一個回調接口:

1 public interface StatementCallback {  
2     Object doInStatement(Statement stmt) throws SQLException;  
3 }  

這時候,我們就要方法的簽名改一下了:

1 private final Object execute(StatementCallback action) throws SQLException  

里面的獲取數據方式也要做如下修改:

1 Object result = action.doInStatement(stmt);//abstract method   

為了看着順眼,我們來給他封裝一層吧:

1 public Object query(StatementCallback stmt) throws SQLException{  
2     return execute(stmt);  
3 }  

OK,大功告成! 
我們來寫一個測試類Test.java測試一下吧: 
這時候,訪問有兩種方式,一種是內部類的方式,一種是匿名方式。 

先來看看內部類的方式:

 1 //內部類方式  
 2     public Object query(final String sql) throws SQLException {  
 3         class QueryStatementCallback implements StatementCallback {  
 4   
 5             public Object doInStatement(Statement stmt) throws SQLException {  
 6                 ResultSet rs = stmt.executeQuery(sql);  
 7                 List<User> userList = new ArrayList<User>();  
 8   
 9                 User user = null;  
10                 while (rs.next()) {  
11   
12                     user = new User();  
13                     user.setId(rs.getInt("id"));  
14                     user.setUserName(rs.getString("user_name"));  
15                     user.setBirth(rs.getDate("birth"));  
16                     user.setCreateDate(rs.getDate("create_date"));  
17                     userList.add(user);  
18                 }  
19                 return userList;  
20   
21             }  
22   
23         }  
24   
25         JdbcTemplate jt = new JdbcTemplate();  
26         return jt.query(new QueryStatementCallback());  
27     }  

在調用jdbcTemplate.query()方法時,傳一個StatementCallBack()的實例過去,也就是我們的內部類。 

再來看看匿名方式:

 1 //匿名類方式  
 2     public Object query2(final String sql) throws Exception{  
 3           
 4         JdbcTemplate jt = new JdbcTemplate();  
 5         return jt.query(new StatementCallback() {  
 6               
 7             public Object doInStatement(Statement stmt) throws SQLException {  
 8                 ResultSet rs = stmt.executeQuery(sql);  
 9                 List<User> userList = new ArrayList<User>();  
10   
11                 User user = null;  
12                 while (rs.next()) {  
13   
14                     user = new User();  
15                     user.setId(rs.getInt("id"));  
16                     user.setUserName(rs.getString("user_name"));  
17                     user.setBirth(rs.getDate("birth"));  
18                     user.setCreateDate(rs.getDate("create_date"));  
19                     userList.add(user);  
20                 }  
21                 return userList;  
22   
23             }  
24         });  
25           
26     } 

相比之下,這種方法更為簡潔。 
為什么spring不用傳統的模板方法,而加之以Callback進行配合呢? 
試想,如果父類中有10個抽象方法,而繼承它的所有子類則要將這10個抽象方法全部實現,子類顯得非常臃腫。而有時候某個子類只需要定制父類中的某一個方法該怎么辦呢?這個時候就要用到Callback回調了。 

離spring jdbcTemplate再近一點 
上面這種方式基本上實現了模板方法+回調模式。但離spring的jdbcTemplate還有些距離。 
我們可以再深入一些。。。 

我們上面雖然實現了模板方法+回調模式,但相對於Spring的JdbcTemplate則顯得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。 
RowMapper接口負責處理某一行的數據,例如,我們可以在mapRow方法里對某一行記錄進行操作,或封裝成entity。 
ResultSetExtractor是數據集抽取器,負責遍歷ResultSet並根據RowMapper里的規則對數據進行處理。 
RowMapper和ResultSetExtractor區別是,RowMapper是處理某一行數據,返回一個實體對象。而ResultSetExtractor是處理一個數據集合,返回一個對象集合。 

當然,上面所述僅僅是Spring JdbcTemplte實現的基本原理,Spring JdbcTemplate內部還做了更多的事情,比如,把所有的基本操作都封裝到JdbcOperations接口內,以及采用JdbcAccessor來管理DataSource和轉換異常等。 

接下來的主題:進行范型的改造 
我們可能發現,上面的接口中返回的還都是Object對象,在范型日益盛行的今天,我們怎能忍受每次都要把得到的對象進行強制類型轉換?那我們就要心情享用JDK1.5給我們帶來的范型盛宴。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM