学习经典:Spring JDBC Framework

       这里记录我对Spring JDBC框架的学习。由于Spring JDBC和我之前做的工作有很多共同之处,学习经典Framework的设计,取长补短,为我所用。
        在这里,先佩服一下Rod JohnSon,他对数据库,JDBC的理解非常深。看Spring jdbc框架的设计和源代码,带给了我很多以前没有想到的东西。
       我们知道,Spring JDBC的主要目标是为了简化JDBC的编程,方便我们构建健壮的应用程序。这里,它的一个基本设计理念,就是将JDBC编程中变化的和不变化的分开。
        在JDBC中,什么是变化的?毫无疑问,SQL语句是变化的。那什么是不变化的?正确的使用JDBC的方式是不变化的。
先看一段代码。(自己的代码就不拿来做示范了 ,先看Rod书吧)
      
java 代码
 
  1. public List getAvailableSeatlds(DataSource ds, int performanceld,  
  2.        int seatType) throws ApplicationException {  
  3.   
  4.      String sql = "SELECT seat_id AS id FROM available_seats " +  
  5.                   "WHERE performance_id = ? AND price_band_id = ?";                                                           
  6.      List seatlds = new LinkedList();  
  7.   
  8.      Connection con = null;  
  9.      PreparedStatement ps = null;  
  10.      ResultSet rs = null;  
  11.      try {  
  12.        con = ds.getConnection();   //1。建立Connection
  13.        ps = con.prepareStatement(sql);  //2。创建preparedStatement
  14.        ps.setlnt(1, performanceld);  //3。设置ps的参数
  15.        ps.setlnt(2, seatType);  
  16.        rs = ps.executeQuery();      //4.执行查询
  17.   
  18.        while (rs.next()) {         //5.解析ResultSet
  19.          int seatld = rs.getlnt(1);  
  20.          seatlds.add(new Integer(seatld));  
  21.        }                                                                                                                      
  22.        rs.close();                //6.关闭资源,做好善后工作。rs,ps,connection
  23.        ps.close();  
  24.      }  
  25.      catch (SQLException ex) {  
  26.        throw new ApplicationException ("Couldn't run query [" + sql + "]", ex);  
  27.      }  
  28.      finally {  
  29.        try {  
  30.          if (con != null)  
  31.            con.close();  //如果没有连接池的话,不要轻易关。connection属于耗费资源:)
  32.        }  
  33.        catch (SQLException ex) {  
  34.          // Log and ignore  
  35.        }  
  36.      }  
  37.      return seatlds;  
  38.    }  

           从上面看,什么是不变的。首先,咱们这个使用JDBC的方式是良好的,正确的,也是不变的,也就是workflow不变。其次,这里头的很多操作是不变的,比如说:关闭资源,处理异常。
        什么是变的?设置PreparedStament的参数是变化的,利用PreparedStatement做什么是变化的。
       还有什么是变的?取得Connection可能是变化的,我们可以从ConnectionPool中取,也可以裸从Database取。
       还有什么是变的?在主工作流之外,还可以对PreparedStament设置一些属性。比如fetchSize等。
       还有什么是变的?解析ResultSet是变的。但是可以抽象,都是从结果集中取得你想要的东西。
     
       很好。经过分析,我们会自然而然的想到Template设计模式。用模板方法来描述我们的工作流。对于固定的操作,我们会把它建模为一些帮助类,利用这些类来完成固定操作,这些操作在Template方法中被调用。
       对于哪些可以变的方法。我们也发现,其实它要实现的功能是一样的。抽象起来,我们可以用一些接口来描述这些功能。比如说数据库连接管理的功能。
      设计取决于我们考虑问题的深度,以及我们对过程划分的粒度。

       下面,我们阅读Spring JDBC Template的代码吧。好好享受一下。下面几个接口是对变化的部分进行建模:)
      
接口:创建PreparedStatement。根据Connection来创建PreparedStatement。
 
  1. public interface PreparedStatementCreator {  
  2.      PreparedStatement createPreparedStatement (Connection conn)  
  3.        throws SQLException;  
  4.    }  

    使用方法就是:
 
  1. PreparedStatementCreator psc = new PreparedStatementCreator() {  
  2.   
  3.      public PreparedStatement createPreparedStatement (Connection conn)  
  4.          throws SQLException {  
  5.        PreparedStatement ps = conn. prepareStatement (  
  6.          "SELECT seat_id AS id FROM available_seats WHERE " +  
  7.          "performance_id = ? AND price_band_id = ?");  
  8.        ps.setInt(1, performanceId);  
  9.        ps.setInt(2, seatType);  
  10.        return ps;  
  11.      }  
  12.    };  

给PreparedStatement设置参数。是对PreparedStatmentCreator的设置ps值的一个补充。
  1. public interface PreparedStatementSetter {  
  2.     void setValues(PreparedStatement ps) throws SQLException;  

      
对ResultSet进行处理。还有具体的子类。
 
  1. public interface RowCallbackHandler {  
  2.      void processRow(ResultSet rs) throws SQLException;  
  3.    }  

       使用方式:
 
  1.  RowCallbackHandler rch = new RowCallbackHandler() {  
  2.    public void processRow(ResultSet rs) throws SQLException {  
  3.      int seatId = rs.getInt(1) ;  
  4.      list.add(new Integer (seatId) );//典型的inner class的应用,list为外部类的变量。   
  5.    }  
  6.  };  
和上面的RowCallbackHandler类似。
 
  1. public interface ResultSetExtractor {    
  2.       Object extractData(ResultSet rs) throws SQLException, DataAccessException;    
  3. }   

      下面是JdbcTemplate中提供的模板方法。该方法完成对数据库的查询:),看看和上面最初的代码有多少不同。这里除了取数据库连接没有之外,其它的操作都已经有了:),并且很健壮。
       这个execute()方法非常关键。
java 代码
 
  1. public Object query(  
  2.             PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)  
  3.             throws DataAccessException {  
  4.   
  5.         Assert.notNull(rse, "ResultSetExtractor must not be null");  
  6.   
  7.         if (logger.isDebugEnabled()) {  
  8.             String sql = getSql(psc); //取得不变的SQL部分。 
  9.             logger.debug("Executing SQL query" + (sql != null ? " [" + sql  + "]" : ""));  
  10.         }  
  11.         return execute(psc, new PreparedStatementCallback() {  
  12.             public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {  
  13.                 ResultSet rs = null;  
  14.                 try {  
  15.                     if (pss != null) {  
  16.                         pss.setValues(ps);//就是给ps来设置参数用的。ps.setInt(1, 0); 
  17.                     }  
  18.   
  19.                     rs = ps.executeQuery();//执行查询  
  20.   
  21.                     ResultSet rsToUse = rs;  
  22.                     if (nativeJdbcExtractor != null) {  
  23.                         rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);  
  24.                     }  
  25.                     return rse.extractData(rsToUse); // ResultSetExtractor从ResultSet中将值取出来就OK了。  
  26.   
  27.                 }  
  28.                 finally {  
  29.                     //最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。  
  30.                     JdbcUtils.closeResultSet(rs);  
  31.                     if (pss instanceof ParameterDisposer) {  
  32.                         ((ParameterDisposer) pss).cleanupParameters();  
  33.                     }  
  34.                 }  
  35.             }  
  36.         });  
  37.     }  


Are you ready?看看execute()方法吧。
java 代码
 
  1. public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)  
  2.             throws DataAccessException {  
  3.   
  4.         Assert.notNull(psc, "PreparedStatementCreator must not be null");  
  5.         Assert.notNull(action, "Callback object must not be null");  
  6.   
  7.         //取得数据库的连接  
  8.         Connection con = DataSourceUtils.getConnection(getDataSource());  
  9.         PreparedStatement ps = null;  
  10.         try {  
  11.             Connection conToUse = con;  
  12.             if (this.nativeJdbcExtractor != null &&  
  13.                     this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {  
  14.                 conToUse = this.nativeJdbcExtractor.getNativeConnection(con);  
  15.             }  
  16.             //创建PreparedStatement  
  17.             ps = psc.createPreparedStatement(conToUse);  
  18.   
  19.             applyStatementSettings(ps);//这个方法是设置ps的一些属性,我平时不用,Spring框架倒是考虑得相当全的说。  
  20.               
  21.             PreparedStatement psToUse = ps;  
  22.             if (this.nativeJdbcExtractor != null) {  
  23.                 psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);  
  24.             }  
  25.             //调用Callback来完成PreparedStatement的设值。就是调用上面的doInPreparedStatement来使用ps。 
  26.             Object result = action.doInPreparedStatement(psToUse);  
  27.            
  28.             SQLWarning warning = ps.getWarnings();  
  29.             throwExceptionOnWarningIfNotIgnoringWarnings(warning);  
  30.             return result;  
  31.         }  
  32.         //如果有错误的话,那么就开始ps.close(), connection.close();  
  33.         catch (SQLException ex) {  
  34.             // Release Connection early, to avoid potential connection pool deadlock  
  35.             // in the case when the exception translator hasn't been initialized yet.  
  36.             if (psc instanceof ParameterDisposer) {  
  37.                 ((ParameterDisposer) psc).cleanupParameters();  
  38.             }  
  39.             String sql = getSql(psc);  
  40.             psc = null;  
  41.             JdbcUtils.closeStatement(ps);  //就是ps.close();
  42.             ps = null;  
  43.             DataSourceUtils.releaseConnection(con, getDataSource()); /  
  44.             con = null;  
  45.             throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);  
  46.         }  
  47.         //不管怎么样,ps.close(), Connection.close()吧,当然这里是releaseConnection。在我的程序中,Connection只有一个,没有ConnectionPool,当然不会去close Connection。一般来讲,如果没有Connection的线程池的话,我们肯定也不会经常的关闭Connection,得到Connection。毕竟这个东西非常耗费资源。  
  48.         finally {  
  49.             if (psc instanceof ParameterDisposer) {  
  50.                 ((ParameterDisposer) psc).cleanupParameters();  
  51.             }  
  52.             JdbcUtils.closeStatement(ps);  
  53.             DataSourceUtils.releaseConnection(con, getDataSource());  
  54.         }  
  55.     }  

JdbcTemplate完成了负责的操作,客户只需要调用query()就可以完成查询操作了。当然,JdbcTemplate会实现很多带其它参数的方法,以方便你的使用。Template设计模式被发扬广大了,我自己的程序中也主要是利用了Template。

       继续看看DataSourceUtils:这个专门用于管理数据库Connection的类。
   
java 代码
 
  1. public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {  
  2.         try {  
  3.             return doGetConnection(dataSource);  
  4.                 ~~~~~~ //这个方法很舒服,Spring Framework中到处有这样的方法。为什么要委派到这个动作方法? 
  5.         }  
  6.         catch (SQLException ex) {  
  7.             throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);  
  8.         }  
  9.     }  

    这里的doGetConnection就稍微复杂一点了。但是如果没有事务同步管理器的话,那就比较简单。
只是在Connection上多了一个ConnecionHolder类用于持有Connection,实现ConnectionPool的一点小功能。
      
java 代码
 
  1. public static Connection doGetConnection(DataSource dataSource) throws SQLException {  
  2.         Assert.notNull(dataSource, "No DataSource specified");  
  3.   
  4.         ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
  5.         ~~~~~//Connection的持有器。通过持有器得到Connection。
  6.         if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {  
  7.             conHolder.requested();  
  8.             if (!conHolder.hasConnection()) {  
  9.                 logger.debug("Fetching resumed JDBC Connection from DataSource");  
  10.                 conHolder.setConnection(dataSource.getConnection());  
  11.             }  
  12.             return conHolder.getConnection();  
  13.         }  
  14.         // Else we either got no holder or an empty thread-bound holder here.  
  15.   
  16.         logger.debug("Fetching JDBC Connection from DataSource");  
  17.         Connection con = dataSource.getConnection();   
  18.         ……
  19.         return con;  
  20.     }  

ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。
 
  1. public class ConnectionHolder extends ResourceHolderSupport {  
  2.       
  3.     private Connection currentConnection; //当前的Connection  
  4.     private ConnectionHandle connectionHandle;  //Connection的处理器,因此可以通过该类完成对connection的管理。  
  5.   
  6.     public ConnectionHolder(Connection connection) {  
  7.         this.connectionHandle = new SimpleConnectionHandle(connection);  
  8.     }  
  9.       
  10.     public ConnectionHolder(ConnectionHandle connectionHandle) {  
  11.         Assert.notNull(connectionHandle, "ConnectionHandle must not be null");  
  12.         this.connectionHandle = connectionHandle;  
  13.     }  
  14.   
  15.     public Connection getConnection() {  
  16.         Assert.notNull(this.connectionHandle, "Active Connection is required");  
  17.         if (this.currentConnection == null) {  
  18.             this.currentConnection = this.connectionHandle.getConnection();  
  19.         }  
  20.         return this.currentConnection;  
  21.     }  
  22.   
  23.     public void released() {  
  24.         super.released();  
  25.         if (this.currentConnection != null) {  
  26.             this.connectionHandle.releaseConnection(this.currentConnection);  
  27.             this.currentConnection = null;  
  28.         }  
  29.     }  

connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:)
 
  1. public interface ConnectionHandle {  
  2.   
  3.     /** 
  4.      * Fetch the JDBC Connection that this handle refers to. 
  5.      */  
  6.     Connection getConnection();  
  7.   
  8.     /** 
  9.      * Release the JDBC Connection that this handle refers to. 
  10.      * @param con the JDBC Connection to release 
  11.      */  
  12.     void releaseConnection(Connection con);  
  13.   
  14. }  

  最后看一下SimpleConnectionHandle,这个ConnectionHandle的简单实现类。就只有一个Connection可管理。如果有多个Connection可管理的话,这里就是ConnectionPool了:)

   
java 代码
 
  1. public class SimpleConnectionHandle implements ConnectionHandle {  
  2.   
  3.     private final Connection connection;  
  4.   
  5.   
  6.     /** 
  7.      * Create a new SimpleConnectionHandle for the given Connection. 
  8.      * @param connection the JDBC Connection 
  9.      */  
  10.     public SimpleConnectionHandle(Connection connection) {  
  11.         Assert.notNull(connection, "Connection must not be null");  
  12.         this.connection = connection;  
  13.     }  
  14.   
  15.     /** 
  16.      * Return the specified Connection as-is. 
  17.      */  
  18.     public Connection getConnection() {  
  19.         return connection;  
  20.     }  
  21.   
  22.     /** 
  23.      * This implementation is empty, as we're using a standard 
  24.      * Connection handle that does not have to be released. 
  25.      */  
  26.     public void releaseConnection(Connection con) {  
  27.     }  
  28.   
  29.   
  30.     public String toString() {  
  31.         return "SimpleConnectionHandle: " + this.connection;  
  32.     }  
  33.   
  34. }  

一路下来,真是很爽。Spring JDBC Framework真的可谓是深耕细作,这里只是管中窥豹了。类的职责设计得非常清除,同时有良好的设计模式支持,同时提供良好的编程接口,用户基本上只需要了结JdbcTemplate的API就可以了。厉害,厉害。


















你可能感兴趣的:(设计模式,spring,sql,框架,jdbc)