6-基于Spring的框架-JDBC——6-2 基本實(shí)現(xiàn)思路

概要

過度

我們前面介紹了 JDBC 的基本使用方法,以及 Spring JDBC 使用方法。我們大概猜測(cè)了一下,Spring JDBC 的核心類 JdbcTemplate其實(shí)就是對(duì)ConnectorStatement的一個(gè)封裝。

我們通過本文對(duì)JdbcTemplate的實(shí)現(xiàn)思路進(jìn)行詳細(xì)閱讀。

內(nèi)容簡(jiǎn)介

JdbcTemplate實(shí)現(xiàn)詳細(xì)介紹。

所屬環(huán)節(jié)

Spring JDBC實(shí)現(xiàn)詳細(xì)介紹。

上下環(huán)節(jié)

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

下文:無

源碼解析

入口

我們從上文的示例代碼入手:

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

當(dāng)然,根據(jù)入?yún)⒌牟煌?,這兩個(gè)函數(shù)有很大的區(qū)別,我們從update入手:

update()

其中update()有兩種接口:

  • 一種是接受PreparedStatementCreatorPreparedStatementSetter。我們稱之為不帶出結(jié)果。
  • 一種是接受PreparedStatementCreatorKeyHolder。我們稱之為帶出結(jié)果。

對(duì)上面的domain的解釋如下:

  • PreparedStatementCreator:可以生成PreparedStatement【不一定可執(zhí)行,可能需要填充參數(shù)】
  • PreparedStatementSetter:為上面的PreparedStatementCreator服務(wù),可以向里面填參數(shù)
  • KeyHolder:用來接受更新列的指定字段

當(dāng)然,針對(duì)第一種,JdbcTemplate還進(jìn)行了n多種封裝,這里不再贅述,我們主要看這兩個(gè)基本方法。

不帶出結(jié)果

我們直接上源碼:

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()函數(shù),根據(jù)名字猜測(cè)是執(zhí)行更新,返回更新涉及的行數(shù)(Count)。我們進(jìn)去看一下:

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

驚詫三連?。。。。。∵@么說來所有的邏輯都在execute()和拉姆達(dá)表達(dá)式里了。

我們后面再細(xì)看execute()。

帶出結(jié)果

直接上源碼:

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

還是同樣的情況,我們發(fā)現(xiàn)所有的函數(shù)都依賴了execute()我們猜測(cè)是在這里實(shí)現(xiàn)了對(duì)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,打印日志【注意,如果設(shè)置了占位符?的話,這里是不會(huì)顯示替換之后的結(jié)果的】
    logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
  }

  // 拿到 Connection
  Connection con = DataSourceUtils.getConnection(obtainDataSource());
  PreparedStatement ps = null;
  try {
    // 得到 PreparedStatement
    ps = psc.createPreparedStatement(con);
    // 防止取出過多數(shù)據(jù)引起的問題,設(shè)置一些邊界值
    applyStatementSettings(ps);
    // 調(diào)用傳入的回調(diào)函數(shù),在這里會(huì)完成拼接入?yún)?、調(diào)用查詢數(shù)據(jù)庫、拼接出參
    T result = action.doInPreparedStatement(ps);
    // 處理報(bào)警
    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;
    // 關(guān)閉 Connection,此處和 conHolder.requested() 呼應(yīng),減到0之后就 關(guān)閉/放回連接池
    DataSourceUtils.releaseConnection(con, getDataSource());
    con = null;
    throw translateException("PreparedStatementCallback", sql, ex);
  } finally {
    if (psc instanceof ParameterDisposer) {
      ((ParameterDisposer) psc).cleanupParameters();
    }
    // 關(guān)閉 Statement 【關(guān)閉后這個(gè)Statement查出的ResultSet就不能繼續(xù)滾動(dòng)了】
    JdbcUtils.closeStatement(ps);
    DataSourceUtils.releaseConnection(con, getDataSource());
  }
}

基本看注釋就能熟悉整個(gè)思路。其中涉及的一些函數(shù)羅列如下:

  • DataSourceUtils.getConnection(obtainDataSource())返回一個(gè)可用的連接
  • DataSourceUtils.releaseConnection(con, getDataSource())釋放這個(gè)連接

我們進(jìn)入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());
  }
}

照例進(jìn)行了委托,此函數(shù)僅做了一些異常轉(zhuǎn)化操作

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

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

  // 此線程沒有占用過Connector,從ds拿一個(gè)
  logger.debug("Fetching JDBC Connection from DataSource");
  Connection con = fetchConnection(dataSource);

  // TODO 當(dāng)前線程是否配置了事務(wù)【同一事務(wù)為了回滾,會(huì)將所有的數(shù)據(jù)庫操作放在同一個(gè)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 這里后面看事務(wù)時(shí)可以關(guān)注一下
      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 事務(wù)相關(guān)的,后面同一看一下
    if (conHolder != null && connectionEquals(conHolder, con)) {
      // It's the transactional Connection: Don't close it.
      conHolder.released();
      return;
    }
  }
    // 非事務(wù)相關(guān)的,調(diào)用完成根據(jù)情況看是歸還線程池還是關(guān)閉
  doCloseConnection(con, dataSource);
}

其中conHolder.released(),conHolder.requested()內(nèi)部的核心邏輯都是對(duì)一個(gè)int值加減,因?yàn)榧词故鞘聞?wù),也是單一線程做的,不涉及多線程,如此操作足夠

我們畫一個(gè)流程圖同一看一下:

1.png

整體思路基本清晰了,我們只需要在對(duì)應(yīng)的回調(diào)函數(shù)中完成自己的邏輯即可。

總結(jié)

現(xiàn)在我們回去看update(),query()的回調(diào)函數(shù)即可,都是基本的JDBC的接口,沒什么可說的了。

計(jì)劃及展望

本文對(duì)Spring JDBC的核心類、核心方法進(jìn)行了介紹,后面不再贅述。

我們后面會(huì)繼續(xù)看看別的基于Spring的框架,因?yàn)檫@些框架我們大部分情況下都只是使用,最多是查一些問題,基本不會(huì)另起爐灶。所以我們需要的是大概熟悉一下Spring XXX中對(duì)XXX的定制情況,并相應(yīng)的熟悉一下XXX的基本API,以方便工作時(shí)排查各種稀奇古怪的問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概要 過度 我們前面基本介紹完了Spring的框架實(shí)現(xiàn)思路,現(xiàn)在我們以前面介紹的知識(shí)為基礎(chǔ),對(duì)常用的一些基于Spr...
    鵬程1995閱讀 252評(píng)論 0 0
  • 慢慢來比較快,虛心學(xué)技術(shù) 數(shù)據(jù)訪問操作:初始化數(shù)據(jù)訪問框架、打開連接、處理各種異常和關(guān)閉連接,任何一步出現(xiàn)異常都有...
    廖小明的賴胖子閱讀 555評(píng)論 0 3
  • Spring JDBC簡(jiǎn)介 先來看看一個(gè)JDBC的例子。我們可以看到為了執(zhí)行一條SQL語句,我們需要?jiǎng)?chuàng)建連接,創(chuàng)建...
    樂百川閱讀 1,801評(píng)論 2 11
  • 11. 事務(wù)管理 11.1 Spring Framework事務(wù)管理介紹 廣泛的事務(wù)支持是Spring Frame...
    此魚不得水閱讀 650評(píng)論 0 0
  • Part 1 越是簡(jiǎn)單的字,越難寫的好看 就像做人一樣,越想簡(jiǎn)單,純粹,越是難得 一撇一捺,是個(gè)“八”也是個(gè)“人”...
    夏槿花開閱讀 253評(píng)論 0 0

友情鏈接更多精彩內(nèi)容