引自 :学习经典: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. String sql = "SELECT seat_id AS id FROM available_seats " +
  4. "WHERE performance_id = ? AND price_band_id = ?";
  5. List seatlds = new LinkedList();
  6. Connection con = null;
  7. PreparedStatement ps = null;
  8. ResultSet rs = null;
  9. try {
  10. con = ds.getConnection();   //1。建立Connection
  11. ps = con.prepareStatement(sql);  //2。创建preparedStatement
  12. ps.setlnt(1, performanceld);  //3。设置ps的参数
  13. ps.setlnt(2, seatType);
  14. rs = ps.executeQuery();      //4.执行查询
  15. while (rs.next()) {         //5.解析ResultSet
  16. int seatld = rs.getlnt(1);
  17. seatlds.add(new Integer(seatld));
  18. }
  19. rs.close();                //6.关闭资源,做好善后工作。rs,ps,connection
  20. ps.close();
  21. }
  22. catch (SQLException ex) {
  23. throw new ApplicationException ("Couldn't run query [" + sql + "]", ex);
  24. }
  25. finally {
  26. try {
  27. if (con != null)
  28. con.close();  //如果没有连接池的话,不要轻易关。connection属于耗费资源:)
  29. }
  30. catch (SQLException ex) {
  31. // Log and ignore
  32. }
  33. }
  34. return seatlds;
  35. }

从上面看,什么是不变的。首先,咱们这个使用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. public PreparedStatement createPreparedStatement (Connection conn)
  3. throws SQLException {
  4. PreparedStatement ps = conn. prepareStatement (
  5. "SELECT seat_id AS id FROM available_seats WHERE " +
  6. "performance_id = ? AND price_band_id = ?");
  7. ps.setInt(1, performanceId);
  8. ps.setInt(2, seatType);
  9. return ps;
  10. }
  11. };
给PreparedStatement设置参数。是对PreparedStatmentCreator的设置ps值的一个补充。
  1. public interface PreparedStatementSetter {
  2. void setValues(PreparedStatement ps) throws SQLException;
  3. }
对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. Assert.notNull(rse, "ResultSetExtractor must not be null");
  5. if (logger.isDebugEnabled()) {
  6. String sql = getSql(psc); //取得不变的SQL部分。
  7. logger.debug("Executing SQL query" + (sql != null ? " [" + sql  + "]" : ""));
  8. }
  9. return execute(psc, new PreparedStatementCallback() {
  10. public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
  11. ResultSet rs = null;
  12. try {
  13. if (pss != null) {
  14. pss.setValues(ps);//就是给ps来设置参数用的。ps.setInt(1, 0);
  15. }
  16. rs = ps.executeQuery();//执行查询
  17. ResultSet rsToUse = rs;
  18. if (nativeJdbcExtractor != null) {
  19. rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
  20. }
  21. return rse.extractData(rsToUse); // ResultSetExtractor从ResultSet中将值取出来就OK了。
  22. }
  23. finally {
  24. //最后的善后工作还是需要做好的:) rs.close(),把ps的相关参数清除掉。
  25.  JdbcUtils.closeResultSet(rs); 
  26. if (pss instanceof ParameterDisposer) {
  27. ((ParameterDisposer) pss).cleanupParameters();
  28. }
  29. }
  30. }
  31. });
  32. }

Are you ready?看看execute()方法吧。

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

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. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  4. ~~~~~//Connection的持有器。通过持有器得到Connection。
  5. if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
  6. conHolder.requested();
  7. if (!conHolder.hasConnection()) {
  8. logger.debug("Fetching resumed JDBC Connection from DataSource");
  9. conHolder.setConnection(dataSource.getConnection());
  10. }
  11. return conHolder.getConnection(); 
  12. }
  13. // Else we either got no holder or an empty thread-bound holder here.
  14. logger.debug("Fetching JDBC Connection from DataSource");
  15. Connection con = dataSource.getConnection();
  16. ……
  17. return con;
  18. }
ConnectionHolder:Connection的持有器。通过ConnectionHandler来完成对Connection的操作:) 典型的委派。
  1. public class ConnectionHolder extends ResourceHolderSupport {
  2. private Connection currentConnection; //当前的Connection
  3. private ConnectionHandle connectionHandle;  //Connection的处理器,因此可以通过该类完成对connection的管理。
  4. public ConnectionHolder(Connection connection) {
  5. this.connectionHandle = new SimpleConnectionHandle(connection);
  6. }
  7. public ConnectionHolder(ConnectionHandle connectionHandle) {
  8. Assert.notNull(connectionHandle, "ConnectionHandle must not be null");
  9. this.connectionHandle = connectionHandle;
  10. }
  11. public Connection getConnection() {
  12. Assert.notNull(this.connectionHandle, "Active Connection is required");
  13. if (this.currentConnection == null) {
  14. this.currentConnection = this.connectionHandle.getConnection();
  15. }
  16. return this.currentConnection;
  17. }
  18. public void released() {
  19. super.released();
  20. if (this.currentConnection != null) {
  21. this.connectionHandle.releaseConnection(this.currentConnection);
  22. this.currentConnection = null;
  23. }
  24. }
connectionHandle 的接口太纯粹了。但是我觉得这个设计太过于细致了:)
  1. public interface ConnectionHandle {
  2. /**
  3. * Fetch the JDBC Connection that this handle refers to.
  4. */
  5. Connection getConnection();
  6. /**
  7. * Release the JDBC Connection that this handle refers to.
  8. * @param con the JDBC Connection to release
  9. */
  10. void releaseConnection(Connection con);
  11. }

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

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

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

Spring JDBC Framework的更多相关文章

  1. Spring JDBC Framework详解——批量JDBC操作、ORM映射

    转自:https://blog.csdn.net/yuyulover/article/details/5826948 一.spring JDBC 概述 Spring 提供了一个强有力的模板类JdbcT ...

  2. spring jdbc 查询结果返回对象、对象列表

    首先,需要了解spring jdbc查询时,有三种回调方式来处理查询的结果集.可以参考 使用spring的JdbcTemplate进行查询的三种回调方式的比较,写得还不错. 1.返回对象(queryF ...

  3. spring jdbc获取插入记录的主键id

    在JDBC3.0规范中,当新增记录时,允许将数据库自动产生的主键值绑定到Statement或PreparedStatement中.使用Statement时,可以通过以下方法绑定主键值: int exe ...

  4. Spring JDBC实现查询

    1 db.properties jdbc.user=root jdbc.password=920614 jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbc ...

  5. Spring JDBC

    转载:博客主页:http://blog.csdn.NET/chszs 一.概述 在Spring JDBC模块中,所有的类可以被分到四个单独的包:1)core即核心包,它包含了JDBC的核心功能.此包内 ...

  6. Spring学习进阶(四) Spring JDBC

    Spring JDBC是Spring所提供的持久层技术.主要目的是降低使用JDBC API的门槛,以一种更直接,更简洁的方式使用JDBC API.在Spring JDBC里用户仅需要做哪些比不可少的事 ...

  7. Spring JDBC常用方法详细示例

    Spring JDBC使用简单,代码简洁明了,非常适合快速开发的小型项目.下面对开发中常用的增删改查等方法逐一示例说明使用方法 1 环境准备 启动MySQL, 创建一个名为test的数据库 创建Mav ...

  8. Spring JDBC 访问MSSQL

    在Spring中对底层的JDBC做了浅层的封装即JdbcTemplate,在访问数据库的DAO层完全可以使用JdbcTemplate完成任何数据访问的操作,接下来我们重点说说Spring JDBC对S ...

  9. Spring JDBC主从数据库配置

    通过昨天学习的自定义配置注释的知识,探索了解一下web主从数据库的配置: 背景:主从数据库:主要是数据上的读写分离: 数据库的读写分离的好处? 1. 将读操作和写操作分离到不同的数据库上,避免主服务器 ...

随机推荐

  1. ZendStudio 代码调试

    F5.单步调试进入函数内部(单步进入)F6.单步调试不进入函数内部(跳过)F7.由函数内部返回到调用处(跳出) F8.一直执行到下一个断点Ctrl+F2:结束调试

  2. URLRewrite 实现方法详解

    所谓的伪静态页面,就是指的URL重写,在ASP.NET中实现非常简单首先你要在你的项目里引用两个DLL:ActionlessForm.dll.URLRewriter.dll,真正实现重写的是 URLR ...

  3. 百度UEditor富文本编辑器去除自动追加p标签

    本篇文章还原了我在遇到这个问题时的解决过程: 找到ueditor.all.js文件,搜索 me.addInputRule(function(root){ 或者直接搜索 //进入编辑器的li要套p标签 ...

  4. C语言编程学习开发的俄罗斯方块小游戏

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  5. 数据库抽象层 pdo

    一 . PDO的连接 $host = "localhost"; $dbname = "hejuntest"; $username = "root&qu ...

  6. pycharm设置连接

    https://blog.csdn.net/u013088062/article/details/50100121

  7. sklearn的train_test_split函数

    train_test_split函数用于将矩阵随机划分为训练子集和测试子集,并返回划分好的训练集测试集样本和训练集测试集标签. from sklearn.model_selection import ...

  8. c++实验2 顺序存储线性表

    线性表顺序存储 实现了动态数组的增删改查  前驱后继  A=AUB 动态数组右移 (1)顺序表存储结构的定义(类的声明): class SeqList { protected: DataType *l ...

  9. Redhat7无法启动mysql

    是这样的,7的这个环境安装了叫MariaDB了 安装MariaDB之后必须先启动MariaDB [root@redhatx ~]# yum -y install mysql [root@redhatx ...

  10. webservice怎么给对方提供报文,即wsdl文件

    1.webservice发布后在网页打开服务,点击服务说明 2.打开这样一个页面,ctrl+s保存网页,后缀改为wsdl,搞定