五、StatementHandler参数绑定、SQL 执行和结果映射处理器

StatementHandler

StatementHandler 接口是 MyBatis 中非常重要的一个接口,其实现类完成 SQL 语句执行中最核心的一系列操作。

StatementHandler 接口的定义如下图所示:
在这里插入图片描述

可以看到,其中提供了创建 Statement 对象(prepare() 方法)、为 SQL 语句绑定实参(parameterize() 方法)、执行单条 SQL 语句(query() 方法和 update() 方法)、批量执行 SQL 语句(batch() 方法)等多种功能

下图展示了 MyBatis 中提供的所有StatementHandler接口实现类,以及它们的继承关系:
在这里插入图片描述

StatementHandler的创建

在Executor执行的时候创建StatementHandler

//SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 创建StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 使用StatementHandler进行参数绑定
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
 }
 // Configuration#newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 通过RoutingStatementHandler创建StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

RoutingStatementHandler

RoutingStatementHandler 这个 StatementHandler 实现,有点策略模式的意味。在 RoutingStatementHandler 的构造方法中,会根据 MappedStatement 中的 statementType 字段值,选择相应的 StatementHandler 实现进行创建,这个新建的 StatementHandler 对象由 RoutingStatementHandler 中的 delegate 字段维护。

RoutingStatementHandler 的构造方法如下:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 下面就是根据MappedStatement的配置,生成一个相应的StatementHandler对
    // 象,并设置到delegate字段中维护
    switch (ms.getStatementType()) {
        case STATEMENT:
            // 创建SimpleStatementHandler对象
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            // 创建PreparedStatementHandler对象
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            // 创建CallableStatementHandler对象
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default: // 抛出异常
            throw new ExecutorException("...");
    }
}
BaseStatementHandler

BaseStatementHandler 只实现了 StatementHandler 接口的 prepare() 方法,其 prepare() 方法实现为新建的 Statement 对象设置了一些参数,例如,timeout、fetchSize 等。BaseStatementHandler 还新增了一个 instantiateStatement() 抽象方法给子类实现,来完成 Statement 对象的其他初始化操作。不过,BaseStatementHandler 中并没有实现 StatementHandler 接口中的数据库操作等核心方法

在BaseStatementHandler的构造方法中会初始化执行 SQL 需要的 Executor 对象、为 SQL 绑定实参的 ParameterHandler 对象以及生成结果对象的 ResultSetHandler 对象

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
        generateKeys(parameterObject);
        boundSql = mappedStatement.getBoundSql(parameterObject);
    }

这里需要关注的是 generateKeys() 方法,其中会通过 KeyGenerator 接口生成主键,我们知道不同数据库的自增 id 生成策略并不完全一样。Oracle DB 是通过sequence 实现自增 id 的,如果使用自增 id 作为主键,就需要我们先获取到这个自增的 id 值,然后再使用;MySQL 在使用自增 id 作为主键的时候,insert 语句中可以不指定主键,在插入过程中由 MySQL 自动生成 id。KeyGenerator 接口支持 insert 语句执行前后获取自增的 id,分别对应 processBefore() 方法和 processAfter() 方法

KeyGenerator

MySQL 在使用自增 id 作为主键的时候,insert 语句中可以不指定主键,在插入过程中由 MySQL 自动生成 id

Jdbc3KeyGenerator 用于获取数据库生成的自增 id(例如 MySQL 那种生成模式),其 processBefore() 方法是空实现,processAfter() 方法会将 insert 语句执行后生成的主键保存到用户传递的实参中

SelectkeyGeneratorOracle 这种不支持自动生成主键自增 id (通过sequence实现)的数据库Mapper 映射文件中的标签会被解析成 SelectkeyGenerator 对象,其中的 executeBefore 属性(boolean 类型)决定了是在 insert 语句执行之前获取主键,还是在 insert 语句执行之后获取主键 id

SimpleStatementHandler

SimpleStatementHandler 是 StatementHandler 的具体实现之一,继承了 BaseStatementHandler 抽象类。SimpleStatementHandler 各个方法接收的是 java.sql.Statement 对象,并通过该对象来完成 CRUD 操作,所以在 SimpleStatementHandler 中维护的 SQL 语句不能存在“?”占位符,填充占位符的 parameterize() 方法也是空实现。

在 instantiateStatement() 这个初始化方法中,SimpleStatementHandler 会直接通过 JDBC Connection 创建 Statement 对象,这个对象也是后续 SimpleStatementHandler 其他方法的入参

PreparedStatementHandler

PreparedStatementHandler 是 StatementHandler 的具体实现之一,也是最常用的 StatementHandler 实现,它同样继承了 BaseStatementHandler 抽象类。PreparedStatementHandler 各个方法接收的是 java.sql.PreparedStatement 对象,并通过该对象来完成 CRUD 操作,在其 parameterize() 方法中会通过前面介绍的 ParameterHandler调用 PreparedStatement.set*() 方法为 SQL 语句绑定参数,所以在 PreparedStatementHandler 中维护的 SQL 语句是可以包含“?”占位符的。

在 instantiateStatement() 方法中,PreparedStatementHandler 会直接通过 JDBC Connection 的 prepareStatement() 方法创建 PreparedStatement 对象,该对象就是 PreparedStatementHandler 其他方法的入参。

public class PreparedStatementHandler extends BaseStatementHandler {

    public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
    }

    @Override
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        int rows = ps.getUpdateCount();
        Object parameterObject = boundSql.getParameterObject();
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
        return rows;
    }

    @Override
    public void batch(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.addBatch();
    }

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.handleResultSets(ps);
    }

    @Override
    public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.handleCursorResultSets(ps);
    }

    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
            String[] keyColumnNames = mappedStatement.getKeyColumns();
            if (keyColumnNames == null) {
                return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
            } else {
                return connection.prepareStatement(sql, keyColumnNames);
            }
        } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            return connection.prepareStatement(sql);
        } else {
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值