在了解了 DataSource 获取连接(Connection) 的实质以及 JdbcTemplate 的通用接口之后,使用 Spring-jdbc 进行数据库相关的操作可以直截了当的利用如下代码进行实现(此处仅展示通过 Java 硬编码的形式进行实现,XML 配置方法类似)。
public static void main(String... args) {
// 定义数据源
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// 配置参数
dataSource.setUrl("jdbc:mysql://:/?" );
dataSource.setUsername("" );
dataSource.setPassword("" );
// 实例化一个 JDBC 工具类
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
// 执行相关 CRUD 操作
jdbcTemplate.execute();
}
回顾第一节所讲的 JdbcTemplate 的 4 个基础方法:
T execute(ConnectionCallback action);
T execute(StatementCallback action);
T execute(PreparedStatementCreator psc, PreparedStatementCallback action);
T execute(CallableStatementCreator csc, CallableStatementCallback action);
四个基础方法都有一个特定的回调函数将通过预配置的 DataSource 得到的 Connection 或 更进一步的 Statement or PreparedStatement or CallableStatement 作为入参来执行定义的唯一方法。
以
为例来了解一下方法的核心逻辑。
public T execute(StatementCallback 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上对参数注入的回调理解有限。