引自 :学习经典: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. App测试从入门到精通之交叉事件测试

    交叉事件测试又叫事件或者叫冲突测试.对于正在运行的应用,若进入短信,电话等其他软件响应的情况,不会影响所测试应用,且会保证应用都能正确运行.下面我来看一下关于交叉测试中,我们测试人员需要考虑的一些测试 ...

  2. 编写高质量代码改善C#程序的157个建议——建议53:必要时应将不再使用的对象引用赋值为null

    建议53:必要时应将不再使用的对象引用赋值为null 在CLR托管的应用程序中,存在一个“根”的概念,类型的静态字段.方法参数.以及局部变量都可以作为“根”的存在(值类型不能作为“根”,只有引用类型的 ...

  3. 如何快速搭建基于python+appium的自动化测试环境

    首先申明本文是基本于Python与Android来快速搭建Appium自动化测试环境: 主要分为以下几个步骤: 前提条件: 1)安装与配置python环境,打开 Python官网,找到“Downloa ...

  4. tornado设置cookie过期时间(expires time)

    具体的tornado设置过期时间的东西, 我也是查资料才发现的, 现在就贴代码吧 用户登录之后, 设置cookie, 我使用set_secure_cookie的, 它默认是有个30天的过期时间, 导致 ...

  5. Android Bundle传递数据

    1.传递普通数据 Intent intent=new Intent(MainActivity.this,TwoActivity.class); Bundle bundle=new Bundle(); ...

  6. python 日志模块工具类

    #!/usr/bin/env python # -*- coding: utf-8 -*- import logging # logName 日志中的某个格式化的字段名,logFile生成的日志文件名 ...

  7. C#在线运行--cmd方法

       此次C#在线运行采用cmd.exe用csc对文件进行编译,然后再运行的思路实现在线运行的效果.不过会生成二个文件(.cs和.exe),可能需要定期清除临时文件夹. 首先利用时间戳生成唯一文件名, ...

  8. 模拟Springboot二:内置tomcat

    既然要将tomcat内置到项目中,并且能够成功的启动项目就要知道 tomcat  做了哪些事情 ,那么就必须先搞明白 一个 普通的web项目是如何被我们本地配置的tomcat启动并运行的 (1). 先 ...

  9. OC语言自定义打印

    1.为了全文通用,选择在PCH文件中写: // // 版权所有:Copyright © 2018年 Lelight. All rights reserved. // 创 建 者: Lelight // ...

  10. JAVA学习必须掌握的框架,不看后悔

    Web应用,最常见的研发语言是Java和PHP. 后端服务,最常见的研发语言是Java和C/C++. 大数据,最常见的研发语言是Java和Python. 可以说,Java是现阶段中国互联网公司中,覆盖 ...