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 個基礎方法:
<T> T execute(ConnectionCallback<T> action);
<T> T execute(StatementCallback<T> action);
<T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action);
<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上對參數注入的回調理解有限。