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上对参数注入的回调理解有限。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值