16StatementHandler:参数绑定、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 的构造方法如下:
|
|
在 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 的核心代码片段如下:
|
|
如果使用像 Oracle 这种不支持自动生成主键自增 id 的数据库时,我们可以使用 SelectkeyGenerator 来生成主键 id。Mapper 映射文件中的
SelectkeyGenerator 中的 processBefore() 方法和 processAfter() 方法都是通过 processGeneratedKeys() 这个私有方法获取主键 id 的,processGeneratedKeys() 方法会执行
|
|
2. ParameterHandler
介绍完 KeyGenerator 接口之后,我们再来看一下 BaseStatementHandler 中依赖的另一个辅助类—— ParameterHandler。
经过前面《13 | 深入分析动态 SQL 语句解析全流程(下)》介绍的一系列 SqlNode 的处理之后,我们得到的 SQL 语句(维护在 BoundSql 对象中)可能包含多个“?”占位符,与此同时,用于替换每个“?”占位符的实参都记录在 BoundSql.parameterMappings 集合中。
ParameterHandler 接口中定义了两个方法:一个是 getParameterObject() 方法,用来获取传入的实参对象;另一个是 setParameters() 方法,用来替换“?”占位符,这是 ParameterHandler 的核心方法。
DefaultParameterHandler 是 ParameterHandler 接口的唯一实现,其 setParameters() 方法会遍历 BoundSql.parameterMappings 集合,根据参数名称查找相应实参,最后会通过 PreparedStatement.set*() 方法与 SQL 语句进行绑定。setParameters() 方法的具体代码如下:
|
|
SimpleStatementHandler
SimpleStatementHandler 是 StatementHandler 的具体实现之一,继承了 BaseStatementHandler 抽象类。SimpleStatementHandler 各个方法接收的是 java.sql.Statement 对象,并通过该对象来完成 CRUD 操作,所以在 SimpleStatementHandler 中维护的 SQL 语句不能存在“?”占位符,填充占位符的 parameterize() 方法也是空实现。
在 instantiateStatement() 这个初始化方法中,SimpleStatementHandler 会直接通过 JDBC Connection 创建 Statement 对象,这个对象也是后续 SimpleStatementHandler 其他方法的入参。
在 query() 方法实现中,SimpleStatementHandler 会直接通过上面创建的 Statement 对象,执行 SQL 语句,返回的结果集由 ResultSetHandler 完成映射,核心代码如下:
|
|
queryCursor() 方法与 query() 方法实现类似,这里就不再赘述。
batch() 方法调用的是 Statement.addBatch() 方法添加批量执行的 SQL 语句,但并不是立即执行,而是等待 Statement.executeBatch() 方法执行时才会批量执行,这点你稍微注意一下即可。
至于 update() 方法,首先会通过 Statement.execute() 方法执行 insert、update 或 delete 类型的 SQL 语句,然后执行 KeyGenerator.processAfter() 方法查询主键并填充相应属性(processBefore() 方法已经在 prepare() 方法中执行过了),最后通过 Statement.getUpdateCount() 方法获取 SQL 语句影响的行数并返回。
PreparedStatementHandler
PreparedStatementHandler 是 StatementHandler 的具体实现之一,也是最常用的 StatementHandler 实现,它同样继承了 BaseStatementHandler 抽象类。PreparedStatementHandler 各个方法接收的是 java.sql.PreparedStatement 对象,并通过该对象来完成 CRUD 操作,在其 parameterize() 方法中会通过前面介绍的 ParameterHandler调用 PreparedStatement.set*() 方法为 SQL 语句绑定参数,所以在 PreparedStatementHandler 中维护的 SQL 语句是可以包含“?”占位符的。
在 instantiateStatement() 方法中,PreparedStatementHandler 会直接通过 JDBC Connection 的 prepareStatement() 方法创建 PreparedStatement 对象,该对象就是 PreparedStatementHandler 其他方法的入参。
PreparedStatementHandler 的 query() 方法、batch() 方法以及 update() 方法与 SimpleStatementHandler 的实现基本相同,只不过是把 Statement API 换成了 PrepareStatement API 而已。下面我们以 update() 方法为例进行简单介绍:
|
|
CallableStatementHandler
CallableStatementHandler 是处理存储过程的 StatementHandler 实现,其 instantiateStatement() 方法会通过 JDBC Connection 的 prepareCall() 方法为指定存储过程创建对应的 java.sql.CallableStatement 对象。在 parameterize() 方法中,CallableStatementHandler 除了会通过 ParameterHandler 完成实参的绑定之外,还会指定输出参数的位置和类型。
在 CallableStatementHandler 的 query()、queryCursor()、update() 方法中,除了处理 SQL 语句本身的结果集(ResultSet 结果集或是影响行数),还会通过 ResultSetHandler 的 handleOutputParameters() 方法处理输出参数,这是与 PreparedStatementHandler 最大的不同。下面我们以 query() 方法为例进行简单分析:
|
|
总结
这一讲我们重点讲解了 MyBatis 中的 StatementHandler 接口及其核心实现,StatementHandler 接口中定义了执行一条 SQL 语句的核心方法。
首先,分析了 RoutingStatementHandler 实现,它可以帮助我们选择真正的 StatementHandler 实现类。
接下来,介绍了 BaseStatementHandler 这个抽象类的实现,同时还详细阐述了其中使用到的 KeyGenerator 和 ParameterHandler。
最后,又介绍了 SimpleStatementHandler、PreparedStatementHandler 等实现,它们基于 JDBC API 接口,实现了完整的 StatementHandler 功能。
下一讲,我们将开始讲解 MyBatis 中另一个核心接口—— Executor 接口,记得按时来听课。
《Java 工程师高薪训练营》
实战训练+面试模拟+大厂内推,想要提升技术能力,进大厂拿高薪,点击链接,提升自己!
-– ### 精选评论
文章作者
上次更新 10100-01-10