【Spring源码分析】29-JdbcTemplat的设计与实现

1、设计原理

在 Spring JDBC中, JdbcTemplate是一个主要的模板类,从类继承关系上来看, JdbcTemplate继承了基类JdbcAccessor和接口类JdbcOperation。在基类 JdbcAccessorl的设计中,对DataSource数据源进行管理和配置。在 JdbcOperation接口中,定义了通过JDBC操作数据库的基本操作方法,而JdbcTemplate提供这些接口方法的实现比如 execute方法、query方法、update方法等。
《【Spring源码分析】29-JdbcTemplat的设计与实现》

JdbcOperation中有一系列execute()方法并且要求传入一个Callbak函数,此接口的其他方法如query()、update()等方法在JdbcTemplate的实现中都是通过调用execute()方法配合Callbak一个实现的。

《【Spring源码分析】29-JdbcTemplat的设计与实现》

JdbcAccessor实现了InitializingBean接口,afterPropertiesSet()方法中要求必须要有一个DataSource,实现JdbcAccessor就等于拥有了数据源。

@Override
public void afterPropertiesSet() {
   if (getDataSource() == null) {
      throw new IllegalArgumentException("Property 'dataSource' is required");
   }
   if (!isLazyInit()) {
      getExceptionTranslator();
   }
}

2、JdbcTemplate的execute实现

下面以JdbcTemplate.execute()为例进一步分析JdbcTemplate中的代码是如何完成使命的。这个方法是在JdbcTemplate中被其他方法调用的基本方法之一,应用程序往往使用这个方法来执行基本的SQL语句。在execute的实现中看到了对数据库进行操作的基本过程,比如需要取得数据库 Connection,根据应用对数据库操作的需要创建数据库的Statement,对数据库操作进行回调,处理数据库异常,最后把数据库 Connection关闭等。这里展示了使用JDBC完成数据库操作的完整过程,熟悉JDBC使用的读者不会感到陌生,只是Springi对这些较为通用的JDBC使用通过 JdbcTemplate进行了一个封装而已。

execute方法的设计时序图。

《【Spring源码分析】29-JdbcTemplat的设计与实现》

execute()方法分为执行静态sql和动态sql两种,执行静态sql的execute()方法参数要求传入一个StatementCallback。

@FunctionalInterface
public interface StatementCallback<T> {
   @Nullable
   T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
@Override
public void execute(final String sql) throws DataAccessException {
   if (logger.isDebugEnabled()) {
      logger.debug("Executing SQL statement [" + sql + "]");
   }
   class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
      @Override
      @Nullable
      public Object doInStatement(Statement stmt) throws SQLException {
         stmt.execute(sql);
         return null;
      }
      @Override
      public String getSql() {
         return sql;
      }
   }
   execute(new ExecuteStatementCallback());
}
@Override
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
   Assert.notNull(action, "Callback object must not be null");
   //从给定的数据源获取连接
   Connection con = DataSourceUtils.getConnection(obtainDataSource());
   Statement stmt = null;
   try {
      stmt = con.createStatement();
      //使用自身的成员属性fetchSize、maxRows、queryTimeout设置给定的Statement的获取大小、最大行数和查询超时属性
      applyStatementSettings(stmt);
      //通过stmt获取结果,如stmt.execute(sql);
      T result = action.doInStatement(stmt);
      //默认ignoreWarnings=true忽略数据库警告信息,否则以debug模式打印警告信息
      handleWarnings(stmt);
      return result;
   }
   catch (SQLException ex) {
      //尽早释放连接,以避免在异常转换程序尚未初始化的情况下潜在的连接池死锁。
      String sql = getSql(action);
      JdbcUtils.closeStatement(stmt);
      stmt = null;
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw translateException("StatementCallback", sql, ex);
   }
   finally {
      JdbcUtils.closeStatement(stmt);
      DataSourceUtils.releaseConnection(con, getDataSource());
   }
}

可以看出通过一个execute()方法就可以取得结果,避免了传统JDBC大量的样板代码。

执行动态sql的execute()方法参数要求传入一个CallableStatementCreator用于创建CallableStatement和一个CallableStatementCallback用于对CallableStatement进行操作的代码的通用回调接口。

@FunctionalInterface
public interface PreparedStatementCreator {
   PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}
@FunctionalInterface
public interface PreparedStatementCallback<T> {
   @Nullable
   T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException;
}
@Override
@Nullable
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(obtainDataSource());
   CallableStatement cs = null;
   try {
      cs = csc.createCallableStatement(con);
      applyStatementSettings(cs);
      T result = action.doInCallableStatement(cs);
      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);
      JdbcUtils.closeStatement(cs);
      cs = null;
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw translateException("CallableStatementCallback", sql, ex);
   }
   finally {
      if (csc instanceof ParameterDisposer) {
         ((ParameterDisposer) csc).cleanupParameters();
      }
      JdbcUtils.closeStatement(cs);
      DataSourceUtils.releaseConnection(con, getDataSource());
   }
}

从上面代码可以看出用户可以将执行sql的动态参数封装到CallableStatementCallback中来对CallableStatement进行设置,如query()方法的实现。

3、JdbcTemplate的query实现

JdbcTemplate中给出的query、update等常用方法的实现,大多都是依赖于前面提到的execute()方法。

query的设计时序图

《【Spring源码分析】29-JdbcTemplat的设计与实现》

@FunctionalInterface
public interface ResultSetExtractor<T> {
   @Nullable
   T extractData(ResultSet rs) throws SQLException, DataAccessException;
}
@Nullable
public <T> T query(
      PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
      throws DataAccessException {
   Assert.notNull(rse, "ResultSetExtractor must not be null");
   logger.debug("Executing prepared SQL query");
   return execute(psc, new PreparedStatementCallback<T>() {
      @Override
      @Nullable
      public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
         ResultSet rs = null;
         try {
            if (pss != null) {
               pss.setValues(ps);
            }
            rs = ps.executeQuery();
            return rse.extractData(rs);
         }
         finally {
            JdbcUtils.closeResultSet(rs);
            if (pss instanceof ParameterDisposer) {
               ((ParameterDisposer) pss).cleanupParameters();
            }
         }
      }
   });
}

query方法是通过使用PreparedStatementCallback的回调方法dolnPreparedStatement来实现的。在回调函数中,可以看到 PreparedStatement的执行,以及查询结果的返回等处理。

4、DataSourceUtils获取Connection

在以上这些对数据库的操作中,使用了辅助类DataSourceUtils。Spring通过这个辅助类来对数据的Connection进行管理,比如利用它来完成打开和关闭Connection等。在数据库应用中,数据库Connection的使用往往与事务管理有很紧密的联系,这里也可以看到与事务处理相关的操作,比如Connection和当前线程的绑定等。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
   Assert.notNull(dataSource, "No DataSource specified");
   /*把对数据库的Connection放到事务管理中进行管理,这里使用在TransactionSynchronizationManager中定义的ThreadLocal变量来和线程绑定数据库连接*/
   /*如果在TransactionSynchronizationManager中巳经有了与当煎线程绑定的数据库连接,那就直接取出来使用*/
   ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
   if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
      conHolder.requested();
      if (!conHolder.hasConnection()) {
         logger.debug("Fetching resumed JDBC Connection from DataSource");
         conHolder.setConnection(fetchConnection(dataSource));
      }
      return conHolder.getConnection();
   }
   // Else we either got no holder or an empty thread-bound holder here.
   logger.debug("Fetching JDBC Connection from DataSource");
   Connection con = fetchConnection(dataSource);
   //当Spring应用使用了PlatformTransactionManager来进行事物管理,
   //会在AbstractPlatformTransactionManager.prepareSynchronization()方法中激活synchronizations
   if (TransactionSynchronizationManager.isSynchronizationActive()) {
      try {
         //对事务中的其他JDBC操作使用相同的连接
         //线程绑定的对象将在事务完成时通过同步删除。
         ConnectionHolder holderToUse = conHolder;
         if (holderToUse == null) {
            holderToUse = new ConnectionHolder(con);
         }
         else {
            holderToUse.setConnection(con);
         }
         //同一个Connection使用计数
         holderToUse.requested();
         TransactionSynchronizationManager.registerSynchronization(
               new ConnectionSynchronization(holderToUse, dataSource));
         holderToUse.setSynchronizedWithTransaction(true);
         if (holderToUse != conHolder) {
            //当前线程与Connection绑定,同一线程使用相同的连接
            TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
         }
      }
      catch (RuntimeException ex) {
         // Unexpected exception from external delegation call -> close Connection and rethrow.
         releaseConnection(con, dataSource);
         throw ex;
      }
   }
   return con;
}

 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/shenchaohao12321/article/details/86188175
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞