Spring-JDBC 源碼學習(1) —— JdbcTemplate

Spring-JDBC 源碼學習(1)

JdbcTemplate

JdbcTemplate 類作爲 org.springframework.core 包下的核心類,對 Java 實現的 JDBC 進行了一定程度的封裝,可以簡化 JDBC 的使用,同時避免許多常見的錯誤。由該類來執行 JDBC 工作流,應用程序只需要提供 SQL 語句就可以獲得 DB 執行結果(當然實際操作上沒有描述的這麼簡單)。

使用 JdbcTemplate 類只需要實現兩個回調接口 PreparedStatementCreator (創建一個 PreparedStatement,給定連接,提供 SQL 語句以及必要的參數), ResultSetExtractor (用於花式獲取結果集)。當然,不實現上述兩個接口也可以進行簡單的數據庫操作,比如只通過 JdbcTemplate 獲取一個連接(Connection) 或者執行一個靜態 SQL Update。

看不懂無所謂,先繼續向下看,會做更詳細的講解。

JdbcAccessor

首先了解 JdbcTemplate 的實現,JdbcTemplate 繼承了 JdbcAccessor 抽象類,該抽象類有兩個主要屬性—— DataSource dataSource & SQLExceptionTranslator exceptionTranslator ,以及一個懶加載標識符 lazyInit

其中,

  • DataSource 可以認爲是一個存儲有與數據庫相關的屬性實例,主要方法包括 Connection getConnection() & Connection getConnection(String username, String password)
  • SQLExceptionTranslator 利用策略模式將 Java 定義的 SQLException 轉換成 Spring 聲明的 DataAccessException。

JdbcAccessor 實現的 InitializingBean 接口,InitializingBean 接口爲 Bean 提供了一個初始化實例的方法 —— afterPropertiesSet() 方法,凡是繼承該接口的類,一般都在類構造方法中執行該方法。同樣的,聲明初始化實例調用的方法還可採用 <bean id="" class="" init-method="myInitMethod"/> 在初始化 bean 時執行 myInitMethod() 方法。具體執行順序爲 :

  1. bean 的屬性注入
  2. 調用 afterPropertiesSet() 方法
  3. 執行 myInitMethod() 方法

此處繼承該接口是爲了實現 DataSource 驗證以及 SQLExceptionTranslator 的懶加載 OR NOT 的選擇。

@Override
public void afterPropertiesSet() {
    if (getDataSource() == null) {  // 判斷是否注入了 DataSource
        throw new IllegalArgumentException("Property 'dataSource' is required");
    }
    if (!isLazyInit()) {    // 根據懶加載標識符選擇執行與否
        getExceptionTranslator();   // 獲取一個 SQLExceptionTranslator 實例
    }
}

該方法在 BeanFactory 完成該 bean 的依賴注入後執行,將首先判斷 DataSource 是否已經被注入,再根據懶加載標識符來決定是否實例化一個 SQLExceptionTranslator 。

JdbcOperations

同時,JdbcTemplate 類實現了 JdbcOperations 接口,該接口定義了基本的 JDBC 操作。基本方法如下:

  1. <T> T execute(ConnectionCallback<T> action);
  2. <T> T execute(StatementCallback<T> action);
  3. <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action);
  4. <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action);

其它的 execute(…) , query(…), update(…) 等最終都將調用上述 4 種方法其一來完成目的。

詳細觀察上述方法的入參, ConnectionCallback, StatementCallback, PreparedStatementCallback 和 CallableStatementCallback 四個 Callback 接口,分別都是函數式接口,其中的唯一方法 doInXXX() 將在 execute() 中被調用,以此實現獲得 ResultSet 並返回 Result 。execute 中調用 doInXXX() 的通用代碼如下 (以 Statement 爲例):

對於不理解回調的同學,請自行了解概念

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    // 通過工具類 DataSourceUtils 獲取一個連接
    Connection con = DataSourceUtils.getConnection(obtainDataSource());
    // 一個 Statement 空實例,PreparedStatement, CallableStatement 類似
    Statement stmt = null;
    try {
        stmt = con.createStatement();   // 通過連接(Connection)獲取一個 Statement
        applyStatementSettings(stmt);   // 配置 Statement 參數
        // 回調執行 doInXXX() 方法, 並獲得 result
        T result = action.doInStatement(stmt);  
        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.
        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());
    }
}

對於 Statement, PreparedStatement, CallableStatement 的區別,首先下圖表現了三個接口的繼承關係。

  • Statement 可以支持靜態 SQL 語句
  • PreparedStatement 支持可變參數的 SQL 語句
  • CallableStatement 支持可變參數的 SQL 語句,並且支持定製 DB 的輸出結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章