Spring-JDBC 源碼學習(5) —— ResultSet

ResultSet

在前幾節已經提到講了數據源、驅動管理器以及 Statement 之後,利用 JDBC 的最重要的目的就是對 DB 進行操作,並獲得預期結果。對於查詢語句而言,結果應該是若干記錄行;就更新語句而言,結果可能是影響的行數。而 Spring-jdbc 對 ResultSet 額外進行的封裝,即是將原本散亂的結果進行一個整合,例如整合成一個(一組)完整的 Bean 來進行展示。

在 JdbcTemplate 中,四個基本方法入參都包括一個回調接口,而在執行回調獲得 ResultSet 之後,方法並不是直接返回,而是進行了一定的操作。

以一個 PreparedStatementCallback 的實例類爲例,在 doInPreparedStatement() 方法中,獲得了 ResultSet ,但是仍通過 rse.extractData(rs) 語句進行了處理後再返回結果

public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
    // 聲明一個 ResultSet 
    ResultSet rs = null;
    try {
        if (pss != null) {  // setValues() 方法填充 PreparedStatement 中的可變參數 ? 
            pss.setValues(ps);
        }
        rs = ps.executeQuery(); // 執行查詢 sql ,獲取結果
        return rse.extractData(rs); // 重點... 該語句一定是對結果進行了一些操作.
    }
    finally {
        JdbcUtils.closeResultSet(rs);
        if (pss instanceof ParameterDisposer) {
            ((ParameterDisposer) pss).cleanupParameters();
        }
    }
}

在來看一下究竟在返回結果前進行了什麼操作。

由於是一個回調接口的實現類,rse 應該在外部方法中。

public <T> T query(
        PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, 
        final ResultSetExtractor<T> rse)
        throws DataAccessException {

    return execute(psc, new PreparedStatementCallback<T>() {
        @Override
        public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
            ... 
        }
    });
}

可以看到 rse 是一個 ResultSetExtractor 的實例。從名稱上可以看出 Extractor 即爲提取器,結果集提取器。

/** 函數式接口,提供的唯一方法爲 extractData(...) */
@FunctionalInterface
public interface ResultSetExtractor<T> {

    @Nullable
    T extractData(ResultSet rs) throws SQLException, DataAccessException;

}

Spring-jdbc 中現有的實現有三個類,其中 RowCallbackHandlerResultSetExtractor 和 RowMapperResultSetExtractor 兩個類從上面類圖及命名即可看出,兩個是作爲一個適配器而存在的,將 extractData() 進行轉換,分別由 RowCallbackHandler 和 RowMapper 實例進行具體操作。而 AbstractLobStreamingResultSetExtractor 類從名稱上看,也是一個類似流操作的實現類(其中的 Lob 指的是 DB 中的 BLOB 與 CLOB 類型,在 Spring-jdbc 中也加入了額外的支持) 。

RowCallbackHandler

從名稱上看,這是一個與 ResultSet 結果集行相關的回調接口。processRow(ResultSet rs) 方法將處理行相關的邏輯。

從它的一個實現類 RowCountCallbackHandler 來說,其最主要的私有屬性 rowCount 即是統計 ResultSet 的結果行數。下面是一個具體使用的該類的案例:

public static void main(String[] args) throws ClassNotFoundException {

    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setUrl("jdbc:mysql://<host>:<port>/<dbName>");
    dataSource.setUsername("<username>");
    dataSource.setPassword("<password>");

    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    RowCountCallbackHandler rcch = new RowCountCallbackHandler();

    jdbcTemplate.query("SELECT * FROM info WHERE id='2018'", 
                        (RowCallbackHandler) rcch);


    System.out.println(rcch.getRowCount()); //獲取結果集行數
    System.out.println(rcch.getColumnCount());  // 獲取結果集列數
    for (String arg : rcch.getColumnNames()) {  // 打印結果集每一列名稱
        System.out.println("ColumnNames : " + arg);
    }
    for (int i : rcch.getColumnTypes()) {   // 打印結果集每一列類型(Types 爲枚舉類)
        System.out.println("ColumnTypes : " + i);
    }
}

具體查看 RowCountCallbackHandler 類的 processRow() 方法可以看到,其獲取列相關信息都來自於 ResultSetMetaData 。而結果行數來源於 Iterator 迭代。

@Override
public final void processRow(ResultSet rs) throws SQLException {
    if (this.rowCount == 0) {
        ResultSetMetaData rsmd = rs.getMetaData();
        this.columnCount = rsmd.getColumnCount();
        this.columnTypes = new int[this.columnCount];
        this.columnNames = new String[this.columnCount];
        for (int i = 0; i < this.columnCount; i++) {
            this.columnTypes[i] = rsmd.getColumnType(i + 1);
            this.columnNames[i] = JdbcUtils.lookupColumnName(rsmd, i + 1);
        }
        // could also get column names
    }
    processRow(rs, this.rowCount++);
}

RowMapper

上面 RowCallbackHandler 接口從邏輯上劃分是用於處理 ResultSet 元數據信息的。而 RowMapper 較上一個接口而言,有更高的實用性。

特別是其實現類 BeanPropertyRowMapper 可以將散亂的 ResultSet 的結果數據整理成一個個 Object Bean 作爲返回結果。而省去了對逐一讀取 ResultSet 行記錄並填充 Bean 屬性的麻煩。

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

BeanPropertyRowMapper<Model> rowMapper = new BeanPropertyRowMapper<>(Model.class);
List<Model> list = jdbcTemplate.query(
                            "SELECT * FROM info WHERE id = '2018'", rowMapper);
/** 獲得的 Model list 的對應屬性將通過 Java 的反射機制進行填充
 *  List<Model> list 即結果
 */

當然,要求是 Model 類中的屬性與 DB table 中的列名保持一致(大小寫無要求)。

而其它兩個類的實現也是基於反射機制,實現其相應的業務邏輯。

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