Spring源码阅读:Spring JDBC 组件的设计与实现

昨天回忆了我在学习JDBC时自己设计的JDBCTemplate(写在上一篇博客中),在使用Spring过程中,有时会用到Spring给我们提供的JdbcTemplate,这里看看Spring是如何实现这个组件的。

在使用Spring JDBC是你要做的工作很少:

从上面的图上可以看出来,使用Spring JDBC,你只需要做四个工作:

1)定义连接参数:也就是定义url,driver,user,password这个几个参数,一般我们会用一个jdbc.properties文件来配置。

2)指定要执行那个sql语句:项目中要用到什么样的SQL语句,Spring是肯定不知道的,只能我们自己设置的。

3)使用PreparedStatement时需要参数,传递哪些参数肯定也是需要我们自己设置的。

4)迭代结果集的过程需要做那些事情,肯定也是需要我们自己写的。

之前我定义的JDBCTemplate需要做的工作有:

1)配置连接参数

2)指定sql语句

3)传递参数

4)处理结果集

综合来看,两者功能是类似的,但是我定义的那个处理能力是有限,例如处理存储过程的方式并没有一个特定的模板。而在Spring中定义的,是一个可用性很好的组件。根据名称就知道它也是使用了模板方法模式,那么它是如何实现的呢?又提供了哪些模板呢?

如何使用 JdbcTemplate

先来复习一下,如何使用Spring JDBC组件。

在Dao层是这样使用JdbcTemplate的:

@Repository
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}
 

与上面的使用关联的Spring Bean Definition是:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <!-- Scans within the base package of the application for @Components to configure as beans -->
    <context:component-scan base-package="org.springframework.docs.test" />
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <context:property-placeholder location="jdbc.properties"/>
</beans>

在Java代码中只需要指定相应的DataSource,就可以获取到JdbcTemplate对象了。

JdbcTemplate说明

JdbcTemplate作为Spring JDBC组件的核心类,很有必要来看看它是如何实现的。

/**

 * <b>This is the central class in the JDBC core package.</b>

 * It simplifies the use of JDBC and helps to avoid common errors.

 * It executes core JDBC workflow, leaving application code to provide SQL

 * and extract results. This class executes SQL queries or updates, initiating

 * iteration over ResultSets and catching JDBC exceptions and translating

 * them to the generic, more informative exception hierarchy defined in the

 * <code>org.springframework.dao</code> package.

 *

 * <p>Code using this class need only implement callback interfaces, giving

 * them a clearly defined contract. The {@link PreparedStatementCreator} callback

 * interface creates a prepared statement given a Connection, providing SQL and

 * any necessary parameters. The {@link ResultSetExtractor} interface extracts

 * values from a ResultSet. See also {@link PreparedStatementSetter} and

 * {@link RowMapper} for two popular alternative callback interfaces.

 *

 * <p>Can be used within a service implementation via direct instantiation

 * with a DataSource reference, or get prepared in an application context

 * and given to services as bean reference. Note: The DataSource should

 * always be configured as a bean in the application context, in the first case

 * given to the service directly, in the second case to the prepared template.

 *

 * <p>Because this class is parameterizable by the callback interfaces and

 * the {@link org.springframework.jdbc.support.SQLExceptionTranslator}

 * interface, there should be no need to subclass it.

 *

 * <p>All SQL operations performed by this class are logged at debug level,

 * using "org.springframework.jdbc.core.JdbcTemplate" as log category.

 */

1)这个类是Spring JDBC core包的主要类。通过上面的说明,可以对JdbcTemplate有个初步的了解:

2)这个类简化了JDBC的使用,有利于避免一些常见的错误。

3)它能够执行JDBC的流程,并且能够将SQL的提供和结果的处理分离。(其实就是说由用户来提供SQL语句,和结果处理)

4)它能够执行SQL的executeQuery,executeUpdate (这两个是Statement、PreparedStatement的方法),能够初始化ResultSet的迭代器。

5)能够帮助我们捕获异常

6)使用这个类编码时,只需要实现相应的callback接口就行了。

常用的接口有:PreparedStatementCreator、ResultSetExtractor、PreparedStatementSetter、RowMapper

7)可以通过在appliction contex中配置DataSource来直接获取JdbcTemplate对象。

8)如果想要使用log4j等来记录日志信息,需要设置:

log4j.logger.org.springframework.jdbc.core.JdbcTemplate=debug

JdbcTemplate的结构

通过上面的类图,就可以了解到:

1)JdbcTemplate通过继承JdbcAccessor,可以从Spring IOC中获取到DataSource.

DataSource是用户在Bean定义文件中配置的。

2)JdbcOperations为JdbcTemplate提供了一些标准的操作接口。接口中的方法都为用户操作数据库提供了极大的便利。

3)JdbcTemplate使用NativeJdbcExtractor用于从各种不同的JDBC厂商或者数据库连接池获取Connection、Statem、ResultSet等,这个类在JdbcTemplate提供的模板方法内部使用。

JdbcTemplate提供的模板

通过对JdbcTemplate提供的那些方便的方法的浏览,发现了这些方法共同特点是都是用来execute方法。再查看execute方法,发现了JdbcTemplate根据JDBC中的Statement的分类,提供了三种execute:

Statement语句模板:

public <T> T execute(StatementCallback<T> action)

CallableStatement语句模板:

public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action)

PreparedStatement语句模板:

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)

// 附加一种,这种很少用到

public <T> T execute(ConnectionCallback<T> action)

根据这几个模板就可以看出来,它也是采用了使用Callback的TemplateMethod模式。接下来,分别了解他们是如何实现的:

Statement语句模板

public <T> T execute(StatementCallback<T> action) throws DataAccessException {

      Assert.notNull(action, "Callback object must not be null");

// 获取到Connection

      Connection con = DataSourceUtils.getConnection(getDataSource());

      Statement stmt = null;

      try {

        Connection conToUse = con;

        if (this.nativeJdbcExtractor != null &&

              this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {

           conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

        }

// 根据connection创建 Statement

        stmt = conToUse.createStatement();

        applyStatementSettings(stmt);

        Statement stmtToUse = stmt;

        if (this.nativeJdbcExtractor != null) {

           stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);

        }

// 在Callback函数中执行SQL语句,并返回结果

        T result = action.doInStatement(stmtToUse);

// 异常处理

        handleWarnings(stmt);

        return result;

      }

      catch (SQLException ex) {

        // Release Connection early, to avoid potential connection pool deadlock

        // in the case when the exception translator hasn‘t been initialized yet.

// 关闭、释放连接

        JdbcUtils.closeStatement(stmt);

        stmt = null;

        DataSourceUtils.releaseConnection(con, getDataSource());

        con = null;

        throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);

      }

      finally {

        JdbcUtils.closeStatement(stmt);

        DataSourceUtils.releaseConnection(con, getDataSource());

      }

   }

看来这三步的操作要在StatementCallback中来完成了。这个模板方法中对使用JDBC的流程都走了一遍。连接的获取、打开、关闭、方法的调用、异常的处理都设计到了,没有完善的有:SQL的设定、执行,结果的处理。

在JdbcTemplate中找一个使用了这个模板的例子:

public void execute(final String sql) throws DataAccessException {

      if (logger.isDebugEnabled()) {

        logger.debug("Executing SQL statement [" + sql + "]");

      }

      class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {

        public Object doInStatement(Statement stmt) throws SQLException {

           stmt.execute(sql);

           return null;

        }

        public String getSql() {

           return sql;

        }

      }

      execute(new ExecuteStatementCallback());

   }

从这个方法可以看出来,SQL语句确实由我们提供,这个模板是在执行SQL操作时才指定SQL语句。这个使用没有对结果的处理

再看查询的例子:

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {

      Assert.notNull(sql, "SQL must not be null");

      Assert.notNull(rse, "ResultSetExtractor must not be null");

      if (logger.isDebugEnabled()) {

        logger.debug("Executing SQL query [" + sql + "]");

      }

      class QueryStatementCallback implements StatementCallback<T>, SqlProvider {

        public T doInStatement(Statement stmt) throws SQLException {

           ResultSet rs = null;

           try {

              rs = stmt.executeQuery(sql);

              ResultSet rsToUse = rs;

              if (nativeJdbcExtractor != null) {

                 rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);

              }

              return rse.extractData(rsToUse);

           }

           finally {

              JdbcUtils.closeResultSet(rs);

           }

        }

        public String getSql() {

           return sql;

        }

      }

      return execute(new QueryStatementCallback());

   }

这个例子,就是包括自定义结果处理的模板使用。如果我们使用这个方法,处理ResultSet时,还得自己写结果集迭代器。JdbcTemplate中还提供了一种更简洁的方式:

public void query(String sql, RowCallbackHandler rch) throws DataAccessException {

      query(sql, new RowCallbackHandlerResultSetExtractor(rch));

   }

// 这个类可以帮我们使用迭代器获取结果集中的每一行

private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> {

      private final RowCallbackHandler rch;

      public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {

        this.rch = rch;

      }

      public Object extractData(ResultSet rs) throws SQLException {

        while (rs.next()) {

           this.rch.processRow(rs);

        }

        return null;

      }

   }

PreparedStatement语句模板 

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)

        throws DataAccessException {

      Assert.notNull(psc, "PreparedStatementCreator must not be null");

      Assert.notNull(action, "Callback object must not be null");

      if (logger.isDebugEnabled()) {

        String sql = getSql(psc);

        logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));

      }

// 获取Connection

      Connection con = DataSourceUtils.getConnection(getDataSource());

      PreparedStatement ps = null;

      try {

        Connection conToUse = con;

        if (this.nativeJdbcExtractor != null &&

              this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {

           conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

        }

// 创建PreparedStatement

        ps = psc.createPreparedStatement(conToUse);

        applyStatementSettings(ps);

        PreparedStatement psToUse = ps;

        if (this.nativeJdbcExtractor != null) {

           psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);

        }

// 执行SQL,获取结果

        T result = action.doInPreparedStatement(psToUse);

        handleWarnings(ps);

        return result;

      }

      catch (SQLException ex) {

        // Release Connection early, to avoid potential connection pool deadlock

        // in the case when the exception translator hasn‘t been initialized yet.

        if (psc instanceof ParameterDisposer) {

           ((ParameterDisposer) psc).cleanupParameters();

        }

        String sql = getSql(psc);

        psc = null;

        JdbcUtils.closeStatement(ps);

        ps = null;

        DataSourceUtils.releaseConnection(con, getDataSource());

        con = null;

        throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);

      }

      finally {

        if (psc instanceof ParameterDisposer) {

           ((ParameterDisposer) psc).cleanupParameters();

        }

        JdbcUtils.closeStatement(ps);

        DataSourceUtils.releaseConnection(con, getDataSource());

      }

   }

在使用PreparedStatement可能会用到的类有:

PreparedStatementCreator:用于指定SQL语句

PreparedStatementSetter:用于给SQL语句中的参数赋值

指定SQL:

private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {
      private final String sql;
      public SimplePreparedStatementCreator(String sql) {
        Assert.notNull(sql, "SQL must not be null");
        this.sql = sql;
      }

      public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
        return con.prepareStatement(this.sql);
      }

      public String getSql() {
        return this.sql;
      }

   }

设置参数,也是通过遍历参数数组的方式:

public void setValues(PreparedStatement ps) throws SQLException {

if (this.args != null) {

for (int i = 0; i < this.args.length; i++) {

Object arg = this.args[i];

doSetValue(ps, i + 1, arg);

}


}


}

这两个操作都是在内部类中实现的。

CallableStatement语句模板

public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action)

        throws DataAccessException {

      Assert.notNull(csc, "CallableStatementCreator must not be null");

      Assert.notNull(action, "Callback object must not be null");

      if (logger.isDebugEnabled()) {

        String sql = getSql(csc);

        logger.debug("Calling stored procedure" + (sql != null ? " [" + sql  + "]" : ""));

      }

      Connection con = DataSourceUtils.getConnection(getDataSource());

      CallableStatement cs = null;

      try {

        Connection conToUse = con;

        if (this.nativeJdbcExtractor != null) {

           conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

        }

        cs = csc.createCallableStatement(conToUse);

        applyStatementSettings(cs);

        CallableStatement csToUse = cs;

        if (this.nativeJdbcExtractor != null) {

           csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs);

        }

        T result = action.doInCallableStatement(csToUse);

        handleWarnings(cs);

        return result;

      }

      catch (SQLException ex) {

        // Release Connection early, to avoid potential connection pool deadlock

        // in the case when the exception translator hasn‘t been initialized yet.

        if (csc instanceof ParameterDisposer) {

           ((ParameterDisposer) csc).cleanupParameters();

        }

        String sql = getSql(csc);

        csc = null;

        JdbcUtils.closeStatement(cs);

        cs = null;

        DataSourceUtils.releaseConnection(con, getDataSource());

        con = null;

        throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex);

      }

      finally {

        if (csc instanceof ParameterDisposer) {

           ((ParameterDisposer) csc).cleanupParameters();

        }

        JdbcUtils.closeStatement(cs);

        DataSourceUtils.releaseConnection(con, getDataSource());

      }

   }

这个看起来和前两个有什么不同吗?

接下来看看JdbcTemplate中的其他方法:

他们都是在这几个模板方法的基础上对回调接口给出的部分实现而已。

Spring源码阅读:Spring JDBC 组件的设计与实现

时间: 2024-08-24 17:22:49

Spring源码阅读:Spring JDBC 组件的设计与实现的相关文章

Spring源码阅读:IOC容器的设计与实现(二)——ApplicationContext

上一主题中,了解了IOC容器的基本概念,以及BeanFactory的设计与实现方式,这里就来了解一下ApplicationContext方式的实现. ApplicationContext 在Spring的参考文档中,为啥要推荐使用ApplicationContext?它能给我们的应用带来什么好处呢?作为BeanFactory的实现之一,它又是如何设计的?在SpringMVC中使用的WebApplictionContext\XmlApplicationContext与之有何关联? Applicat

Spring源码阅读系列总结

最近一段时间,粗略的查看了一下Spring源码,对Spring的两大核心和Spring的组件有了更深入的了解.同时在学习Spring源码时,得了解一些设计模式,不然阅读源码还是有一定难度的,所以一些重要的设计模式简单的做了阐述.同时还会简单的加入一些GOF中提到的设计原则.Spring的源码阅读系列,也暂告一段落.下面是就带你走进Spring世界: Spring系列的引子 1)Spring WebApplicationContext初始化与消亡 这一节帮我们了解Spring是如何初始化WebAp

Spring源码阅读:Spring事务管理的基础

上一节了解了全局事务与局部事务以及Spring提供的两种事务模式:编程式事务与声明式事务. 不论是编程式的事务处理,还是声明式的事务处理.他们都要对局部事务和全局事务以支持,也就是说要对JDBC进行支持.ORM框架,同时也要对JTA进行支持.他们的公共部分是commit,rollback.通过这一节的了解,我相信以后配置Spring事务时,就不需要在去网上查资料了或者去查Spring的参考文档了. 因此,Spring设计了如下的事务管理框架: 从上面的类图中和容易可以看出分为三部分:Platfo

spring源码阅读(2)-- 容器启动之加载BeanDefinition

在<spring源码阅读(1)-- 容器启动之资源定位>一文中,阅读了spring是怎么根据用户指定的配置加载资源,当加载完资源,接下来便是把从资源中加载BeanDefinition. BeanDefinition作为spring其中一个组件,spring是这样描述BeanDefinition的:BeanDefinition描述了一个bean实例,它具有属性值,构造函数参数值以及具体实现提供的更多信息.个人的理解是BeanDefinition保存了一个bean实例的所有元数据,下面列举一些常用

Spring源码阅读:Spring MVC 如何处理HTTP请求

Spring MVC 对HTTP请求的处理流程 通过之前的源码阅读,知道了ApplicationContext初始的过程,也知道了Spring MVC环境的初始化过程,今天就来了解一下SpringMVC是如何处理HTTP请求的. HTTP请求根据请求方式可以分为GET.POST.PUT.DELETE.OPTIONS.TRACE,最常用的还是GET和POST. Spring对于这几种HTTP请求的处理都是使用了processRequest(req,rep); @Override protected

Spring源码阅读:Spring声明式事务处理和编程式事务处理的设计与实现

之前的学习,了解了Spring事务管理的基础框架(查看).Spring在此基础上又提到了声明式事务管理和编程式事务管理.这里就来看看Spring是如何实现的. Spring声明式事务与EJB事务管理对比 Spring的声明式管理,类似于EJB的CMT,但又有不同.他们的不同之处有: 1)EJB的CMT是与JTA结合使用,而Spring框架的声明式事务管理可以在任何环境下工作.既可以使用全局事务管理,如JTA,也可以使用局部事务管理如JDBCJPA.Hibernate.JDO等. 2)可以在任何类

Spring源码阅读:Spring如何支持各种ORM框架

为了让开发程序更容易,到现在为止,已经有很多ORM框架了,例如:JPA,JDO,Hibernate,Mybatis(之前版本是IBatis)等等.也正因为已经有这么多优秀的ORM框架,Spring团队并没有自己开发一套ORM框架,而是对这些框架都进行了支持,让这些框架在Spring环境下可以得到完全的应用. 通常,在Spring环境下使用这些ORM框架时,都会通过一个Template来使用.Spring对这些框架的集成是这样的: 例如Hibernate,在使用Hibernate时(没有在Spri

Spring源码阅读:Spring AOP设计与实现(一):动态代理

在Spring的有两个核心:IOC与AOP,AOP又是基于动态代理模式实现的.所以要了解SpringAOP是如何设计的之前,还是先了解一下Java中的动态代理比较好. 认识代理模式 代理模式是这么描述的: 代理模式是为其他对象提供一种代理以控制对这个对象的访问 代理对象的功能: 通过创建一个代理对象,用这个代理对象去代理真实的对象,客户端得到这个代理对象后,对客户端并没有什么影响,就跟真实的对象一样(因为代理对象和真是对象实现了同一接口). 下面看看代理模式的类图: 解说: RealSubjec

Spring源码阅读:Spring WebApplicationContext初始化与消亡

使用SpringMVC时,需要不论是使用注解配置,还是使用XML配置Bean,他们都会在Web服务器启动后就初始化.根据J2ee的知识可以知道,肯定是使用了ServletContextListener才完成的这个功能.那Spring又是如何实现的呢?还有我们在Web.xml配置的那些applicationContext.xml相关的XML文件的位置(配置方式多样),又是如何读取到相应的文件的呢,读取到这些文件后,是如何初始化类的呢?我们能不能自定义初始化过程或者自定义WebApplication