Spring-JDBC 源碼學習(4) —— PreparedStatement & CallableStatement

PreparedStatement & CallableStatement

在瞭解了 DataSource 獲取連接(Connection) 的實質以及 JdbcTemplate 的通用接口之後,使用 Spring-jdbc 進行數據庫相關的操作可以直截了當的利用如下代碼進行實現(此處僅展示通過 Java 硬編碼的形式進行實現,XML 配置方法類似)。

public static void main(String... args) {
    // 定義數據源
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    // 配置參數
    dataSource.setUrl("jdbc:mysql://<host>:<port>/<dbName>?<props>");
    dataSource.setUsername("<username>");
    dataSource.setPassword("<passwd>");

    // 實例化一個 JDBC 工具類
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    // 執行相關 CRUD 操作 
    jdbcTemplate.execute();
}

回顧第一節所講的 JdbcTemplate 的 4 個基礎方法:

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

四個基礎方法都有一個特定的回調函數將通過預配置的 DataSource 得到的 Connection 或 更進一步的 Statement or PreparedStatement or CallableStatement 作爲入參來執行定義的唯一方法。

<T> T execute(StatementCallback<T> action); 爲例來了解一下方法的核心邏輯。

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() {

    } finally {

    }
}

但是,對於 PreparedStatement 和 CallableStatement 而言,獲取一個 *XXX*Statement 的方式就有所不同了。

// 獲取 Statement 實例
Statement stmt = con.createStatement();
// 獲取 PreparedStatement 實例
// psc 是一個 PreparedStatementCreator 接口實現的實例
PreparedStatement ps = psc.createPreparedStatement(con);
// 獲取 CallableStatement 實例
// csc 是一個 CallableStatementCreator 接口實現的實例
CallableStatement cs = csc.createCallableStatement(con);

可以看到 Statement 直接通過 Connection 獲取實例,但是 PreparedStatement 和 CallableStatement 就有所不同,其區別就在於 PreparedStatement 和 CallableStatement 兩個 Statement 都是可以定製入參的,更甚者, CallableStatement 可以定製 DB 執行結果的出參。當然,核心還是 con.prepareStatement() OR con.prepareCall() 方法,只不過是將獲取 XXXStatement 的操作下放給 XXXStatementCreator 實例類實現,給予使用者重複的自主權,同時也是邏輯解耦的一種操作。

例如:

SimplePreparedStatementCreator 這個 PreparedStatementCreator 接口的實現類,只是簡單的調用了 con.prepareStatement(sql) 方法。

private static class SimplePreparedStatementCreator 
    implements PreparedStatementCreator, SqlProvider {

    private final String sql;

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

而對於 PreparedStatementCreatorImpl ,在 createPreparedStatement(Connection con) 的實現上,又添加了更多的操作。

private class PreparedStatementCreatorImpl
        implements PreparedStatementCreator, PreparedStatementSetter, SqlProvider, ParameterDisposer {

    private final String actualSql;

    private final List<?> parameters;

    @Override
    public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
        PreparedStatement ps;
        if (generatedKeysColumnNames != null || returnGeneratedKeys) {
            if (generatedKeysColumnNames != null) {
                // 獲取一個 PreparedStatement 實例,下同
                ps = con.prepareStatement(this.actualSql, 
                                          generatedKeysColumnNames);
            }
            else {
                ps = con.prepareStatement(this.actualSql, 
                                         PreparedStatement.RETURN_GENERATED_KEYS);
            }
        }
        else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY 
                 && !updatableResults) {
            ps = con.prepareStatement(this.actualSql);
        }
        else {
            ps = con.prepareStatement(this.actualSql, resultSetType,
                updatableResults ? ResultSet.CONCUR_UPDATABLE : 
                                      ResultSet.CONCUR_READ_ONLY);
        }
        // 將可變 SQL (例如 SELECT * FROM msg WHERE id = ?) 的 ? 用實際參數替換
        setValues(ps);
        return ps;
    }
}

從上述兩種 PreparedStatementCreator 接口的不同實現,也可以從另一種角度理解到,函數式接口將獲取目標出參的具體邏輯交給使用者定義,給予了使用者充分的自主權,同時也是一種業務邏輯的解耦。

在上面代碼 PreparedStatementCreatorImpl 類的實現中,我們看到第 32 行代碼 setValue(ps) 。此處的方法是由接口 PreparedStatementSetter 定義的。主要目的是將可變 SQL 中的 ? 參數用實際參數進行一個替換。

// 以 SQL (SELECT * FROM msg WHERE id = ? AND day = ?) 爲例
/** 純 Java 核心庫的實現 PreparedStatement 參數注入 */
ps.setInt(1, 1763);
ps.setString(2, "2018-01-01");
ps.executeQuery();

/** 以 Spring-jdbc 實現 PreparedStatement 參數注入 */
// setValues() 由接口 PreparedStatementSetter 定義,封裝了注入參數的具體實現邏輯
// 可以由使用者自行定義
setValues(ps);  
ps.executeQuery();

上面類圖表示了 PreparedStatementSetter 及其實現類的相關依賴。

Setter 的主要目標即爲對 SQL 中的 ? 參數進行注入。

個人精力有限,對Spring-jdbc在Statement上對參數注入的回調理解有限。

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