欢迎关注公众号 【11来了】 ,持续 MyBatis 源码系列内容!
在我后台回复 「资料」 可领取
编程高频电子书
!
在我后台回复「面试」可领取硬核面试笔记
!文章导读地址:点击查看文章导读!
感谢你的关注!
MyBatis 源码系列文章:
(一)【MyBatis 源码拆解系列】MyBatis 源码如何学习?
(二)【MyBatis 源码拆解系列】MyBatis 运行原理 - 读取 xml 配置文件
(三)MyBatis 运行原理 - MyBatis 的核心类 SqlSessionFactory 和 SqlSession
(四)MyBatis 运行原理 - MyBatis 中的代理模式
四、MyBatis 的数据库操作最终由哪些类负责?
本节主要介绍内容如下:
这里就是最后一个步骤了,调用 UserMapper 接口的方法进行查询,底层是如何去执行对应的 SQL 查询数据库呢?
// 5、调用接口展开数据库操作
List<User> userList = userMapper.queryUserBySchoolName(userParam);
在通过代理模式获取了 UserMapper 接口的代理对象之后,那么这里调用 userMapper 其实调用的就是 MyBatis 内部创建的代理对象了,然后就会走到拦截器 MapperProxy 的 invoke() 方法中,那么这里直接看 invoke() 方法:
// MyBatis 源码 binding 包下的 MapperProxy 类
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果调用的 Object 的方法:equals()、toString()、hashCode() 等,则不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 核心方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
这里直接看核心方法,先将 method -> MapperMethod 的映射缓存起来,然后将方法的执行都交给 MapperMethod 即可,接下来看 MapperMethod 的 execute() 如何执行:
// MyBatis 源码 binding 包下的 MapperMethod 类
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// ...
}
case UPDATE: {
// ...
}
case DELETE: {
// ...
}
case SELECT:
// ...
} else if (method.returnsMany()) {
// 这里查询的是 List 集合,因此会走到下边这行
result = executeForMany(sqlSession, args);
}
// ...
}
return result;
}
这里为了清晰,将其他的代码给省略掉,也就是根据方法的类型以及返回结果的类型判断走哪一个方法,这里会走到 executeForMany() 方法内部,如下:
// MyBatis 源码 binding 包下的 MapperMethod 类
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
// 1、查询参数的转换
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
// ...
} else {
// 2、执行 SQL 查询
result = sqlSession.selectList(command.getName(), param);
}
// ...
return result;
}
这里先进行查询参数的转换,再进行 SQL 查询,最终 SQL 查询还是由 SqlSession 来完成,之前看了 SqlSession 内部都是 selectList()、selectOne() 等数据库查询方法,进入内部看细节:
// MyBatis 源码 session 包下的 DefaultSqlSession 类
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1、先拿到 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 2、通过执行器去完成查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里先通过 statement 去获取对应的 MappedStatement,statement 其实就是执行的 UserMapper 方法的全限定类名 + 方法名:
通过 statement 作为唯一标识,去获取对应的 MappedStatement,他肯定会存储一些执行 SQL 中需要的信息,具体存储什么信息这里先不关心,重要的是单独抽取出来一个 MappedStatement 来存储对应信息这个思想,部分信息如下:
最终将真正的执行动作交给执行器 Executor,这里会走到 CachingExecutor,涉及 装饰器模式 ,对其他的几种 Executor 进行包装增强,提供了二级缓存的功能,这里暂时先不讲二级缓存实现细节,先从整体对 MyBatis 整个执行流程有一个大致的了解,在看源码的初始阶段, 先清除哪个功能大致对应哪个模块,再根据功能点进行细看即可
至此,MyBatis 整体的运行流程大致介绍完成了,为了可以对整体有一个直观的认识,还有很多细节性的内容并没有介绍,比如如何解析 UserMapper.xml 文件中的 SQL、Executor 内部具体如何去执行 SQL 语句、SQL 语句返回的结果如何进行封装等等,这些会放在后边,通过单个功能点进行详细拆解,透视内部执行原理
总结:
MyBatis 内部源码有个特点,每个方法都很短,在查看源码的时候会在不同的类之间不断跳转,为什么呢?为了保持类的单一职责,将功能不断地进行抽取,比如:
- UserMapper 被 MapperProxy 代理,代理对象的创建交给 MapperProxyFactory
- UserMapper 中的每个方法都有对应的 MapperMethod 来管理
- 具体的 SQL 执行都是交给 MapperMethod,通过判断查询类型以及返回类型,进行不同类型的处理之后再走到 SqlSession 中,在这里获取 MappedStatement 之后,将具体 SQL 的执行又交给执行器 Executor,基础的执行器 Executor 负责 SQL 的执行,CachingExecutor 对基础的执行器 Executor 进行包装增强,提供了二级缓存的功能
可以感受到,通过将类的职责范围不断缩小,通过类名可以很清晰地描述整个执行流程,这样我们在以后开发中,也就不需要一直去写大量的 Service 服务来完成需求了,也可以针对一些功能创建不同的类来完成