(转帖)学习经典:Spring JDBC Framework_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 4261 | 回复: 0   主题: (转帖)学习经典:Spring JDBC Framework        下一篇 
ab19890824
注册用户
等级:少尉
经验:351
发帖:92
精华:0
注册:2011-11-21
状态:离线
发送短消息息给ab19890824 加好友    发送短消息息给ab19890824 发消息
发表于: IP:您无权察看 2011-11-24 20:28:13 | [全部帖] [楼主帖] 楼主

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

  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.             thrownew 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; 

从上面看,什么是不变的。首先,咱们这个使用JDBC的方式是良好的,正确的,也是不变的,也就是workflow不变。其次,这里头的很多操作是不变的,比如说:关闭资源,处理异常。
什么是变的?设置PreparedStament的参数是变化的,利用PreparedStatement做什么是变化的。
还有什么是变的?取得Connection可能是变化的,我们可以从ConnectionPool中取,也可以裸从Database取。
还有什么是变的?在主工作流之外,还可以对PreparedStament设置一些属性。比如fetchSize等。
还有什么是变的?解析ResultSet是变的。但是可以抽象,都是从结果集中取得你想要的东西。

很好。经过分析,我们会自然而然的想到Template设计模式。用模板方法来描述我们的工作流。对于固定的操作,我们会把它建模为一些帮助类,利用这些类来完成固定操作,这些操作在Template方法中被调用。
对于哪些可以变的方法。我们也发现,其实它要实现的功能是一样的。抽象起来,我们可以用一些接口来描述这些功能。比如说数据库连接管理的功能。
设计取决于我们考虑问题的深度,以及我们对过程划分的粒度。

下面,我们阅读Spring JDBC Template的代码吧。好好享受一下。下面几个接口是对变化的部分进行建模:)

接口:创建PreparedStatement。根据Connection来创建PreparedStatement。

  1. publicinterface PreparedStatementCreator { 
  2.       
  3.       PreparedStatement createPreparedStatement (Connection conn) 
  4.       throws SQLException; 

    使用方法就是:

  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. publicinterface PreparedStatementSetter { 
  2.       void setValues(PreparedStatement ps) throws SQLException; 



对ResultSet进行处理。还有具体的子类。

  1. publicinterface RowCallbackHandler { 
  2.       void processRow(ResultSet rs) throws SQLException; 


    使用方式:

  1.  RowCallbackHandler rch = new RowCallbackHandler() { 
  2.       
  3.       publicvoid processRow(ResultSet rs) throws SQLException { 
  4.             
  5.             int seatId = rs.getInt(1) ; 
  6.             list.add(new Integer (seatId) );//典型的inner class的应用,list为外部类的变量。 
  7.       } 
  8. };

和上面的RowCallbackHandler类似。

  1. publicinterface ResultSetExtractor { 
  2.       
  3.       Object extractData(ResultSet rs) throws SQLException, DataAccessException; 

    下面是JdbcTemplate中提供的模板方法。该方法完成对数据库的查询:),看看和上面最初的代码有多少不同。这里除了取数据库连接没有之外,其它的操作都已经有了:),并且很健壮。
这个execute()方法非常关键。

  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.       }); 

看看execute()方法吧。

  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.       } 


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

继续看看DataSourceUtils:这个专门用于管理数据库Connection的类。

  1. publicstatic Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { 
  2.       try { 
  3.             return doGetConnection(dataSource); 
  4.             ~~~~~~ //这个方法很舒服,Spring Framework中到处有这样的方法。为什么要委派到这个动作方法?
  5.       } 
  6.       catch (SQLException ex) { 
  7.             thrownew CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); 
  8.       } 

    这里的doGetConnection就稍微复杂一点了。但是如果没有事务同步管理器的话,那就比较简单。
只是在Connection上多了一个ConnecionHolder类用于持有Connection,实现ConnectionPool的一点小功能。

  1. publicstatic Connection doGetConnection(DataSource dataSource) throws SQLException { 
  2.       
  3.       Assert.notNull(dataSource, "No DataSource specified"); 
  4.       
  5.       ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); 
  6.       ~~~~~//Connection的持有器。通过持有器得到Connection。
  7.       if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { 
  8.             conHolder.requested(); 
  9.             if (!conHolder.hasConnection()) { 
  10.                   logger.debug("Fetching resumed JDBC Connection from DataSource"); 
  11.                   conHolder.setConnection(dataSource.getConnection()); 
  12.             } 
  13.             return conHolder.getConnection(); 
  14.       } 
  15.       // Else we either got no holder or an empty thread-bound holder here.
  16.       
  17.       logger.debug("Fetching JDBC Connection from DataSource"); 
  18.       Connection con = dataSource.getConnection(); 
  19.       ……
  20.       return con; 

ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。

  1. publicclass 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.             returnthis.currentConnection; 
  21.       } 
  22.       
  23.       publicvoid released() { 
  24.             super.released(); 
  25.             if (this.currentConnection != null) { 
  26.                   this.connectionHandle.releaseConnection(this.currentConnection); 
  27.                   this.currentConnection = null; 
  28.             } 
  29.       } 

connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:)

  1. publicinterface ConnectionHandle { 
  2.       
  3.       
  4.       /**
  5.       * Fetch the JDBC Connection that this handle refers to.
  6.       */
  7.       Connection getConnection(); 
  8.       
  9.       /**
  10.       * Release the JDBC Connection that this handle refers to.
  11.       * @param con the JDBC Connection to release
  12.       */
  13.       void releaseConnection(Connection con); 
  14.       

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

  1. publicclass SimpleConnectionHandle implements ConnectionHandle { 
  2.       
  3.       privatefinal 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.       publicvoid releaseConnection(Connection con) { 
  27.       } 
  28.       
  29.       
  30.       public String toString() { 
  31.             return"SimpleConnectionHandle: " + this.connection; 
  32.       } 
  33.       

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




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论