主题
《面渣逆袭》MyBatis 篇 · 第 2/3 章。原版 PDF(下载 / 打印)
15. 能说说MyBatis的工作原理吗?
我们已经大概知道了MyBatis 的工作流程,按工作原理,可以分为两大步:生成会话工厂、会话运
行。

MyBatis 是一个成熟的框架,篇幅限制,这里抓大放小,来看看它的主要工作流程。
构建会话工厂
构造会话工厂也可以分为两步:

获取配置获取配置这一步经过了几步转化,最终由生成了一个配置类Configuration 实例,这个配置类实例非常重要,主要作用包括:
读取配置文件,包括基础配置文件和映射文件初始化基础配置,比如MyBatis 的别名,还有其它的一些重要的类对象,像插件、映射器、
ObjectFactory 等等提供一个单例,作为会话工厂构建的重要参数它的构建过程也会初始化一些环境变量,比如数据源
java
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
//省略异常处理
//xml配置构建器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//通过转化的Configuration构建SqlSessionFactory
var5 = this.build(parser.parse());
}构建SqlSessionFactory
SqlSessionFactory 只是一个交叉,构建出来的实际上是它的实现类的实例,一般我们用的都是它的实现类DefaultSqlSessionFactory,
java
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}会话运行
会话运行是MyBatis 最复杂的部分,它的运行离不开四大组件的配合:

Executor (执行器)
Executor 起到了至关重要的作用,SqlSession 只是一个门面,相当于客服,真正干活的是是
Executor ,就像是默默无闻的工程师。它提供了相应的查询和更新方法,以及事务方法。
Environment environment = this.configuration.getEnvironment();TransactionFactorytransactionFactory=
java
this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//通过Configuration创建executor
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);StatementHandler (数据库会话器)
StatementHandler ,顾名思义,处理数据库会话的。我们以SimpleExecutor 为例,看一下它的查询方法,先生成了一个StatementHandler 实例,再拿这个handler 去执行query 。
java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();StatementHandlerhandler=configuration.newStatementHandler(this.wrapper,ms,
parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}再以最常用的PreparedStatementHandler看一下它的query 方法,其实在上面的prepareStatement已经对参数进行了预编译处理,到了这里,就直接执行s ql ,使用ResultHandler 处理返回结果。
java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws
SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}ParameterHandler (参数处理器)
PreparedStatementHandler里对s ql 进行了预编译处理
java
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}这里用的就是ParameterHandler ,setParameters 的作用就是设置预编译SQL 语句的参数。
里面还会用到typeHandler 类型处理器,对类型进行处理。
java
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement var1) throws SQLException;
}ResultSetHandler (结果处理器)
我们前面也看到了,最后的结果要通过ResultSetHandler 来进行处理,handleResultSets 这个方法就是用来包装结果集的。Mybatis 为我们提供了一个DefaultResultSetHandler,通常都是用这个实现类去进行结果的处理的。
java
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement var1) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}它会使用typeHandle 处理类型,然后用ObjectFactory 提供的规则组装对象,返回给调用者。
整体上总结一下会话运行:

我们最后把整个的工作流程串联起来,简单总结一下:

- 读取 MyBatis 配置文件— — mybatis-config.xml 、加载映射文件— — 映射文件即 SQL 映射文
件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。
构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory 。
创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
Executor 执行器:MyBatis 底层定义了一个 Executor 交叉来操作数据库,它将根据
SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
StatementHandler :数据库会话器,串联起参数映射的处理和运行结果映射的处理。
参数处理:对输入参数的类型进行处理,并预编译。
结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。
16. MyBatis的功能架构是什么样的?

我们一般把Mybatis 的功能架构分为三层:
API 交叉层:提供给外部使用的交叉API ,开发人员通过这些本地API 来操纵数据库。交叉层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
17. 为什么Mapper交叉不需要实现类?
四个字回答:动态代理,我们来看一下获取Mapper 的过程:

获取Mapper我们都知道定义的Mapper 交叉是没有实现类的,Mapper 映射其实是通过动态代理实现的。
BlogMapper mapper = session.getMapper(BlogMapper.class);七拐八绕地进去看一下,发现获取Mapper 的过程,需要先获取MapperProxyFactory — — Mapper代理工厂。
java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T>mapperProxyFactory=
java
(MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5,
var5);
}
}
}MapperProxyFactory
MapperProxyFactory 的作用是生成MapperProxy (Mapper 代理对象)。
java
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;… …
java
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]
{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {MapperProxy<T>mapperProxy=newMapperProxy(sqlSession,this.mapperInterface,
this.methodCache);
return this.newInstance(mapperProxy);
}
}这里可以看到动态代理对交叉的绑定,它的作用就是生成动态代理对象(占位),而代理的方法被放到了MapperProxy 中。
MapperProxy
MapperProxy 里,通常会生成一个MapperMethod 对象,它是通过cachedMapperMethod 方法对其进行初始化的,然后执行excute 方法。
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) :
this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}MapperMethod
MapperMethod 里的excute 方法,会真正去执行s ql 。这里用到了命令模式,其实绕一圈,最终它还是通过SqlSession 的实例去运行对象的s ql 。
java
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;… …
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null ||
!this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;… …
}18.Mybatis都有哪些Executor执行器?

Mybatis 有三种基本的Executor 执行器,SimpleExecutor 、ReuseExecutor 、BatchExecutor 。
SimpleExecutor:每执行一次update 或select ,就开启一个Statement 对象,用完立刻关闭
Statement 对象。
ReuseExecutor:执行update 或select ,以s ql 作为k ey 查找Statement 对象,存在就使用,不存在就创建,用完后,不关闭Statement 对象,而是放置于M ap<String,Statement >内,供下一次使用。简言之,就是重复使用Statement 对象。
BatchExecutor:执行update (没有select ,JDBC 批处理不支持select ),将所有s ql 都添加到批
处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个
Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor 的这些特点,都严格限制在SqlSession 生命周期范围内。
Mybatis 中如何指定使用哪一种Executor 执行器?
在Mybatis 配置文件中,在设置(settings )可以指定默认的ExecutorType 执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession 的方法传递ExecutorType 类型参数,如
SqlSession openSession(ExecutorType execType) 。配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared
statements ); BATCH 执行器将重用语句并执行批量更新。