learn-tech/专栏/深入剖析MyBatis核心原理-完/16StatementHandler:参数绑定、SQL执行和结果映射的奠基者.md
2024-10-16 06:37:41 +08:00

15 KiB
Raw Permalink Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        16  StatementHandler参数绑定、SQL 执行和结果映射的奠基者
                        StatementHandler 接口是 MyBatis 中非常重要的一个接口,其实现类完成 SQL 语句执行中最核心的一系列操作,这也是后面我们要介绍的 Executor 接口实现的基础。

StatementHandler 接口的定义如下图所示:

StatementHandler 接口中定义的方法

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

下图展示了 MyBatis 中提供的所有 StatementHandler 接口实现类,以及它们的继承关系:

StatementHandler 接口继承关系图

今天这一讲我们就来详细分析该继承关系图中每个 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("...");

}

}

在 RoutingStatementHandler 的其他方法中,都会委托给底层的 delegate 对象来完成具体的逻辑。

BaseStatementHandler

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

了解了 BaseStatementHandler 对 StatementHandler 接口的实现情况之后,我们再来看一下 BaseStatementHandler 的构造方法,其中会初始化执行 SQL 需要的 Executor 对象、为 SQL 绑定实参的 ParameterHandler 对象以及生成结果对象的 ResultSetHandler 对象。这三个核心对象中ResultSetHandler 对象我们已经在[《14 | 探究 MyBatis 结果集映射机制背后的秘密(上)》]中介绍过了ParameterHandler 和 Executor 在后面会展开介绍。

  1. KeyGenerator

这里需要关注的是 generateKeys() 方法,其中会通过 KeyGenerator 接口生成主键,下面我们就来看看 KeyGenerator 接口的相关内容。

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

KeyGenerator 接口继承关系图

Jdbc3KeyGenerator 用于获取数据库生成的自增 id例如 MySQL 那种生成模式),其 processBefore() 方法是空实现processAfter() 方法会将 insert 语句执行后生成的主键保存到用户传递的实参中。我们在使用 MyBatis 执行单行 insert 语句时,一般传入的实参是一个 POJO 对象或是 Map 对象,生成的主键会设置到对应的属性中;执行多条 insert 语句时,一般传入实参是 POJO 对象集合或 Map 对象的数组或集合,集合中每一个元素都对应一次插入操作,生成的多个自增 id 也会设置到每个元素的相应属性中。

Jdbc3KeyGenerator 中获取数据库自增 id 的核心代码片段如下:

// 将数据库生成的自增id作为结果集返回

try (ResultSet rs = stmt.getGeneratedKeys()) {

final ResultSetMetaData rsmd = rs.getMetaData();

final Configuration configuration = ms.getConfiguration();

if (rsmd.getColumnCount() < keyProperties.length) {

} else {

    // 处理rs这个结果集将生成的id设置到对应的属性中

    assignKeys(configuration, rs, rsmd, keyProperties, parameter);

}

} catch (Exception e) {

throw new ExecutorException("...");

}

如果使用像 Oracle 这种不支持自动生成主键自增 id 的数据库时,我们可以使用 SelectkeyGenerator 来生成主键 id。Mapper 映射文件中的标签会被解析成 SelectkeyGenerator 对象,其中的 executeBefore 属性boolean 类型)决定了是在 insert 语句执行之前获取主键,还是在 insert 语句执行之后获取主键 id。

SelectkeyGenerator 中的 processBefore() 方法和 processAfter() 方法都是通过 processGeneratedKeys() 这个私有方法获取主键 id 的processGeneratedKeys() 方法会执行标签中指定的 select 语句,查询主键信息,并记录到用户传入的实参对象的对应属性中,核心代码片段如下所示:

// 创建一个新的Executor对象来执行指定的select语句

Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);

// 拿到主键信息

List