美文网首页
6-基于Spring的框架-JDBC——6-2 基本实现思路

6-基于Spring的框架-JDBC——6-2 基本实现思路

作者: 鹏程1995 | 来源:发表于2020-02-25 16:39 被阅读0次

概要

过度

我们前面介绍了 JDBC 的基本使用方法,以及 Spring JDBC 使用方法。我们大概猜测了一下,Spring JDBC 的核心类 JdbcTemplate其实就是对ConnectorStatement的一个封装。

我们通过本文对JdbcTemplate的实现思路进行详细阅读。

内容简介

JdbcTemplate实现详细介绍。

所属环节

Spring JDBC实现详细介绍。

上下环节

上文: Spring框架介绍,Spring JDBC引入

下文:无

源码解析

入口

我们从上文的示例代码入手:

jdbcTemplate.update();
jdbcTemplate.query();

当然,根据入参的不同,这两个函数有很大的区别,我们从update入手:

update()

其中update()有两种接口:

  • 一种是接受PreparedStatementCreatorPreparedStatementSetter。我们称之为不带出结果。
  • 一种是接受PreparedStatementCreatorKeyHolder。我们称之为带出结果。

对上面的domain的解释如下:

  • PreparedStatementCreator:可以生成PreparedStatement【不一定可执行,可能需要填充参数】
  • PreparedStatementSetter:为上面的PreparedStatementCreator服务,可以向里面填参数
  • KeyHolder:用来接受更新列的指定字段

当然,针对第一种,JdbcTemplate还进行了n多种封装,这里不再赘述,我们主要看这两个基本方法。

不带出结果

我们直接上源码:

protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
  throws DataAccessException {

  logger.debug("Executing prepared SQL update");

  return updateCount(execute(psc, ps -> {
    try {
      if (pss != null) {
        pss.setValues(ps);
      }
      int rows = ps.executeUpdate();
      if (logger.isDebugEnabled()) {
        logger.debug("SQL update affected " + rows + " rows");
      }
      return rows;
    } finally {
      if (pss instanceof ParameterDisposer) {
        ((ParameterDisposer) pss).cleanupParameters();
      }
    }
  }));
}

整体思路比较明确,都委托给了updateCount()函数,根据名字猜测是执行更新,返回更新涉及的行数(Count)。我们进去看一下:

private static int updateCount(@Nullable Integer result) {
  Assert.state(result != null, "No update count");
  return result;
}

惊诧三连!!!!!!这么说来所有的逻辑都在execute()和拉姆达表达式里了。

我们后面再细看execute()

带出结果

直接上源码:

@Override
public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
  throws DataAccessException {

  Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
  logger.debug("Executing SQL update and returning generated keys");

  return updateCount(execute(psc, ps -> {
    int rows = ps.executeUpdate();
    List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
    generatedKeys.clear();
    ResultSet keys = ps.getGeneratedKeys();
    if (keys != null) {
      try {
        RowMapperResultSetExtractor<Map<String, Object>> rse =
          new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1);
        generatedKeys.addAll(result(rse.extractData(keys)));
      } finally {
        JdbcUtils.closeResultSet(keys);
      }
    }
    if (logger.isDebugEnabled()) {
      logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
    }
    return rows;
  }));
}

情况基本相似,等后面过完execute()再回头看。

query()

上源码:

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

还是同样的情况,我们发现所有的函数都依赖了execute()我们猜测是在这里实现了对jdbc通用方法的封装。

execute()

@Override
@Nullable
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); // 拿到sql,打印日志【注意,如果设置了占位符?的话,这里是不会显示替换之后的结果的】
    logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
  }

  // 拿到 Connection
  Connection con = DataSourceUtils.getConnection(obtainDataSource());
  PreparedStatement ps = null;
  try {
    // 得到 PreparedStatement
    ps = psc.createPreparedStatement(con);
    // 防止取出过多数据引起的问题,设置一些边界值
    applyStatementSettings(ps);
    // 调用传入的回调函数,在这里会完成拼接入参、调用查询数据库、拼接出参
    T result = action.doInPreparedStatement(ps);
    // 处理报警
    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;
    // 关闭 Connection,此处和 conHolder.requested() 呼应,减到0之后就 关闭/放回连接池
    DataSourceUtils.releaseConnection(con, getDataSource());
    con = null;
    throw translateException("PreparedStatementCallback", sql, ex);
  } finally {
    if (psc instanceof ParameterDisposer) {
      ((ParameterDisposer) psc).cleanupParameters();
    }
    // 关闭 Statement 【关闭后这个Statement查出的ResultSet就不能继续滚动了】
    JdbcUtils.closeStatement(ps);
    DataSourceUtils.releaseConnection(con, getDataSource());
  }
}

基本看注释就能熟悉整个思路。其中涉及的一些函数罗列如下:

  • DataSourceUtils.getConnection(obtainDataSource())返回一个可用的连接
  • DataSourceUtils.releaseConnection(con, getDataSource())释放这个连接

我们进入DataSourceUtils看一下:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
  try {
    return doGetConnection(dataSource);
  } catch (SQLException ex) {
    throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
  } catch (IllegalStateException ex) {
    throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
  }
}

照例进行了委托,此函数仅做了一些异常转化操作

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
  Assert.notNull(dataSource, "No DataSource specified");

  // 获得一个链接,如果本线程保存的变量中有就返回
  ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  // 本线程占了一个 Connector,重入次数+1,然后返回
  if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
    conHolder.requested();
    if (!conHolder.hasConnection()) {
      logger.debug("Fetching resumed JDBC Connection from DataSource");
      // 如果holder中的不可用,就再拿一个
      conHolder.setConnection(fetchConnection(dataSource));
    }
    return conHolder.getConnection();
  }
  // Else we either got no holder or an empty thread-bound holder here.

  // 此线程没有占用过Connector,从ds拿一个
  logger.debug("Fetching JDBC Connection from DataSource");
  Connection con = fetchConnection(dataSource);

  // TODO 当前线程是否配置了事务【同一事务为了回滚,会将所有的数据库操作放在同一个Connector中】
  if (TransactionSynchronizationManager.isSynchronizationActive()) {
    try {
      // Use same Connection for further JDBC actions within the transaction.
      // Thread-bound object will get removed by synchronization at transaction completion.
      ConnectionHolder holderToUse = conHolder;
      if (holderToUse == null) {
        holderToUse = new ConnectionHolder(con);
      } else {
        holderToUse.setConnection(con);
      }
      holderToUse.requested();// 重入+1
      // TODO 这里后面看事务时可以关注一下
      TransactionSynchronizationManager.registerSynchronization(
        new ConnectionSynchronization(holderToUse, dataSource));
      holderToUse.setSynchronizedWithTransaction(true);
      if (holderToUse != conHolder) {
        // 这里保存到线程
        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
      }
    } catch (RuntimeException ex) {
      // Unexpected exception from external delegation call -> close Connection and rethrow.
      // 和主逻辑的释放一样
      releaseConnection(con, dataSource);
      throw ex;
    }
  }

  return con;
}

我们接下来看一下释放:

public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) {
  try {
    doReleaseConnection(con, dataSource);
  } catch (SQLException ex) {
    logger.debug("Could not close JDBC Connection", ex);
  } catch (Throwable ex) {
    logger.debug("Unexpected exception on closing JDBC Connection", ex);
  }
}

public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
  if (con == null) {
    return;
  }
  if (dataSource != null) {
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    // TODO 事务相关的,后面同一看一下
    if (conHolder != null && connectionEquals(conHolder, con)) {
      // It's the transactional Connection: Don't close it.
      conHolder.released();
      return;
    }
  }
    // 非事务相关的,调用完成根据情况看是归还线程池还是关闭
  doCloseConnection(con, dataSource);
}

其中conHolder.released()conHolder.requested()内部的核心逻辑都是对一个int值加减,因为即使是事务,也是单一线程做的,不涉及多线程,如此操作足够

我们画一个流程图同一看一下:

1.png

整体思路基本清晰了,我们只需要在对应的回调函数中完成自己的逻辑即可。

总结

现在我们回去看update(),query()的回调函数即可,都是基本的JDBC的接口,没什么可说的了。

计划及展望

本文对Spring JDBC的核心类、核心方法进行了介绍,后面不再赘述。

我们后面会继续看看别的基于Spring的框架,因为这些框架我们大部分情况下都只是使用,最多是查一些问题,基本不会另起炉灶。所以我们需要的是大概熟悉一下Spring XXX中对XXX的定制情况,并相应的熟悉一下XXX的基本API,以方便工作时排查各种稀奇古怪的问题。

相关文章

网友评论

      本文标题:6-基于Spring的框架-JDBC——6-2 基本实现思路

      本文链接:https://www.haomeiwen.com/subject/roqxchtx.html