目录
1. 前提
2. Executor执行器
3. 总结
4. 三个火枪手
5. StatementHandler生成Statement
6. ParameterHandler 参数解析
7. BoundSql的数据结构
8. 总结
在Mybatis源码分析_事务管理器 (5)_chen_yao_kerr的博客-CSDN博客一文中,我们已经对模板设计模式进行了介绍。下面强调一下概念以及Executor组件是如何使用模板设计模式的。
模板模式(Template Pattern)
一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定实现;
Executor是MyBaits核心接口之一,定义了数据库操作最基本的方法,SqlSession的功能都是基于它来实现的。
BaseExecutor是一个抽象基类,实现了executor接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery等方法。
比如,关闭事务的close方法、query方法、commit提交方法、缓存key生成的 createCacheKey方法等,都是在这个BaseExecutor基类中重写的。
1. update 方法调用doUpdate方法,这个doUpdate方法是个抽象方法,延迟到子类进行实现;2. queryFromDatabase方法调用了doQuery 方法,而这个doQuery也是延迟到子类实现的。
这些都是典型的 模板设计模式。
SimpleExecutor :默认配置,使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象,用完后关闭。
ReuseExecutor:可重用执行器,会将statement存入map中。使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存map中的statement对象;
BatchExecutor:实现批量执行多条SQL语句的能力,基于jdbc的batch实现批处理
ClosedExecutor:这个执行器我并没有接触过,没看见过谁使用过
CachingExecutor: 使用装饰器模式,对真正提供数据库查询的 Executor 增强了二级缓存
的 能 力 ; 二 级 缓 存 初 始 化 位 置 DefaultSqlSessionFactory.openSessionFromDataSource(ExecutorType,
TransactionIsolationLevel, boolean);
思考:SimpleExecutor 和 ReuseExecutor 区别是重用缓存statement对象,那么ReuseExecutor是如何设计的呢?
思考:既然有这么多的执行器,那么我们如何选择想要使用的执行器呢?
其实,我们在mybatis的全局xml文件中,直接配置就可以了,如下图:
1. SqlSessionFactory负责生产SqlSession
2. SqlSession查询数据库,其实底层是依赖Executor组件完成的。而具体使用哪一个子类型的Executor可以通过全局xml进行配置。
3. Executor执行器,其实是一个指挥官。它在调度三个小弟StatementHandler、ParameterHandler、ResultSetHandler进行工作
此时,我们就引出来了支持Statement的三个火枪手了。分别是StatementHandler、ParameterHandler 和 ResultSetHandler。
StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用;
ParameterHandler:对预编译的SQL语句进行参数设置,SQL语句中的的占位符“?”都对应BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;
他们的运行关系如下:
StatementHandler、ParameterHandler、ResultSetHandler都只是接口。StatementHandler完成Mybatis最核心的工作,也是Executor实现的基础;功能包括:创建statement对象,为sql语句绑定参数,执行增、删、改、查等SQL语句、将结果映射集进行转化;
BaseStatementHandler:所有子类的抽象父类,定义了初始化statement的操作顺序,由子类实现具体的实例化不同的statement(模板模式);
RoutingStatementHandler:Excutor组件真正实例化的子类,使用静态代理模式,根据上下文决定创建哪个具体实体类;
SimpleStatmentHandler :使用statement对象访问数据库,无须参数化; PreparedStatmentHandler :使用预编译PrepareStatement对象访问数据库; CallableStatmentHandler :调用存储过程
首先,我们的执行器Executor需要依赖全局遍历Configuration的newStatementHandler 方法来创建具体的StatementHandler对象。见下图:
而进入Configuration对象,我们发现它就是new了一个 RoutingStatementHandler对象
而我们new出来的 RoutingStatementHandler 才是决定我们具体使用哪一个子类型的StatementHandler的核心。它负责根据我们的mybatis配置信息,生成不同的具体的StatmentHandler,这边是静态代理 + 简单工厂的改造版本,不好具体定义到底是哪个设计模式
而我们可以为每一个sql语句指定不同的StatementType类型。默认使用的是PREPARED,即预编译模式。这样就和上方的代码一一对应起来了。
那么,拿到了RoutingStatementHandler 以后能干嘛呢?
先看传统的JDBC访问数据库:
此处,我们以SimpleExecutor为例子,接着往下看:
进入方法以后,我们发现,它和传统的JDBC几乎一模一样
a. 生成Connection对象
b. 生成Statement对象
c. 处理占位符以及参数设置,最终返回Statement
继续跟进,看看它具体是如何生成Statement的:
动态代理技术,反射调用
进入基类 BaseStatementHandler。 模板设计模式的体现
最终,调到 PreparedStatementHandler, 生成预编译的PreparedStatement
其实,参数设置也是利用动态代理技术,进行参数设置的
上面已经提到了参数解析了,它是封装到Statement对象中的。本节将对于参数解析进行更加详细的分析。
/*
* Copyright ${license.git.copyrightYears} the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.scripting.defaults;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class DefaultParameterHandler implements ParameterHandler {
//typeHandler注册中心
private final TypeHandlerRegistry typeHandlerRegistry;
//对应的sql节点的信息
private final MappedStatement mappedStatement;
//用户传入的参数
private final Object parameterObject;
//SQL语句信息,其中还包括占位符和参数名称信息
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//从boundSql中获取sql语句的占位符对应的参数信息
List parameterMappings = boundSql.getParameterMappings();
//遍历这个参数列表,把参数设置到PreparedStatement中
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);//获得入参的形参名字,javatype,jdbctype等信息
if (parameterMapping.getMode() != ParameterMode.OUT) {//对于存储过程中的参数不处理
Object value;//绑定的实参值
String propertyName = parameterMapping.getProperty();//参数的名字
// 获取对应的实参值
if (boundSql.hasAdditionalParameter(propertyName)) { //是否为附加参数
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {//是否为空
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//是否要进行指定的类型转换
value = parameterObject;
} else {//大部分情况走这段
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取typeHandler对象
JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的jdbcType
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//为statment中的占位符绑定参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
ParameterHandler相对而言比较简单,它就2个方法。一个是获取参数值,一个是设置参数值。获取参数值比较简单,下面我们把重点放在设置参数值上进行分析。
在Mybatis源码分析_解析大流程梳理_补充 (4)_chen_yao_kerr的博客-CSDN博客一文中,我们重点说过SqlSource对象,它是负责封装mybatis中具体的SQL语句以及参数的。而通过SqlSource我们就可以获取到BoundSql对象,它就是负责存储SQL语句的。
下面看代码,在我们执行查询的时候,我们是根据第一阶段封装的MappedStatement获取到BoundSql对象的。
而MappedStatement对象内部又封装了SqlSource对象,我们是直接从SqlSource对象获取的BoundSql对象。具体获取过程如下:
Ok,有了BoundSql对象以后,我们就是执行上面的PreparedStament生成操作了。但是,生成PreparedStatement以后,我们是要进行预编译和参数绑定的。其实上面已经说过了,下面我再帖一遍代码:
通过上图我们知道,参数绑定是通过StatementHandler对象的parameterize方法完成的。进入以后是放射调用,并且调用的是 RoutingStatementHandler 的 parameterize方法,而RoutingStatementHandler 此时内部持有Statement对象,准确的说是 PreparedStatementHandler的对象(为什么是PreparedStatementHandler对象,上面已经解释过)。
因此,它必然进入PreparedStatementHandler的parameterize方法中。 而PreparedStatementHandler则调用了ParameterHandler的setParameters方法。如下图:
因此,最终会进入ParameterHandler的setParameters方法
也就是说,只要获取到正确的BoundSql就可以搞定一切。而参数值,是我们在执行查询的时候传递进去的,Executor调度MappedStatement获取BoundSql传入进入的,而实际上是传递给MappedStatement内部的SqlSource对象的,由SqlSource对象来生成BoundSql对象的。
1. 在我们生成PreparedStatement的时候,我们会进行预处理并且设置参数
2. 设置参数的过程是,调用第一阶段生成的MappedStatement对象的SqlSource来生成的
3. 在我们实际调用的阶段,参数值会传递给MappedStatement对象的SqlSource,有它来负责生成BoundSql对象
最后,就是根据BoundSql对象进行数据库的查询。 下一章我们说返回值ResultSetHandler