• Spring JDBC Framework


    引自 :学习经典:Spring JDBC Framework

    这里记录我对Spring JDBC框架的学习。由于Spring JDBC和我之前做的工作有很多共同之处,学习经典Framework的设计,取长补短,为我所用。
            在这里,先佩服一下Rod JohnSon,他对数据库,JDBC的理解非常深。看Spring jdbc框架的设计和源代码,带给了我很多以前没有想到的东西。
           我们知道,Spring JDBC的主要目标是为了简化JDBC的编程,方便我们构建健壮的应用程序。这里,它的一个基本设计理念,就是将JDBC编程中变化的和不变化的分开。
            在JDBC中,什么是变化的?毫无疑问,SQL语句是变化的。那什么是不变化的?正确的使用JDBC的方式是不变化的。
    先看一段代码。
          

    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就可以了。厉害,厉害。

  • 相关阅读:
    为什么puppeteer比selenium好?
    Puppeteer
    js跳出多层循环
    webpack loader- 图片处理
    webpack的loader的原理和实现
    Webpack中Loader的pitch方法
    url-loader和file-loader区别
    Vue中强制组件重新渲染的正确方法
    ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION :浏览器下载报错
    JSBridge的原理及使用
  • 原文地址:https://www.cnblogs.com/shisw/p/3635838.html
Copyright © 2020-2023  润新知