MyBatis 源码解析:架构初探

MyBatis 是一个易用、轻量,且强大的半自动化 ORM 框架,在设计思想和代码实现上都有许多值得我们借鉴的地方,例如动态代理机制的应用,资源文件的加载与解析,缓存模块的设计、反射机制的应用,插件模块的设计,架构分层,以及对设计模式的应用等,是一个非常适合初入源码阅读领域的练手项目。本系列文章将从配置文件解析、映射文件解析,以及 SQL 语句执行机制这三个大方向对整个框架的实现展开分析。本文是本系列的第一篇文章,主要从整体的角度对 MyBatis 的架构设计做一个综述性的介绍。

在正式开始之前,先以一个小例子演示一下 MyBatis 的基本使用。MyBatis 目前已经同时支持 XML 和注解的方式编写 SQL 语句,虽然注解在 Spring 中极大的提升了使用体验,但是对于 MyBatis 而言,个人还是比较倾向于 XML 配置 SQL 语句。下面的示例采用 XML 配置的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="BaseResultMap" type="org.zhenchao.mybatis.entity.User">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="username" jdbcType="VARCHAR" property="username"/>
<result column="password" jdbcType="VARCHAR" property="password"/>
<result column="age" jdbcType="INTEGER" property="age"/>
<result column="phone" jdbcType="VARCHAR" property="phone"/>
<result column="email" jdbcType="VARCHAR" property="email"/>
</resultMap>

<sql id="Base_Column_List">
id, username, `password`, age, phone, email
</sql>

<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from t_user
where username = #{name,jdbcType=VARCHAR}
</select>

接下来,我们就可以编写代码基于 MyBatis 执行数据库操作,示例如下:

1
2
3
4
5
6
7
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
try (SqlSession sqlSession = sessionFactory.openSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectByName("zhenchao");
// ... use user object
}

这个小例子演示了查询指定名称对应的用户信息的操作,MyBatis 可以基于传递的参数动态生成对应的 SQL 语句。后面的源码分析章节,我们将围绕这个小例子去探究 MyBatis 加载解析资源文件、绑定实参并执行方法对应的 SQL 语句,以及最终返回目标实体对象的过程。下面先对 MyBatis 的运行机制做一个简单的概括,先从整体上对该框架的执行过程有一个感知。

MyBatis 是基于配置的框架,它包含两大类型的配置文件,即 mybatis-config.xml 和 Mapper 接口对应的 SQL 语句配置。这里先约定一下,后面的文章中我们会将前者称为 配置文件 ,而将后者称为 映射文件 。MyBatis 框架在启动时会加载并解析这两大类配置,整个过程对应上述示例中的第一行代码。当完成了对框架的初始化过程,接下来我们就可以创建数据库会话,获取 Mapper 对象并执行目标数据库操作,这一过程可以用下面这幅时序图进行描述:

image

SqlSession 是 MyBatis 对外提供的执行数据库操作的统一接口,表示一次数据库会话,所以在具体操作数据库之前需要先开启会话(即获取 SqlSession 对象)。然后需要告知 MyBatis 当前期望操作的具体 Mapper 接口,MyBatis 规定所有的 Mapper 需要定义成接口的形式,这主要是配合 JDK 自带的动态代理机制。MyBatis 会基于动态代理机制为当前 Mapper 接口创建对应的代理对象,并激活对象的 InvocationHandler#invoke 方法。在方法执行过程中判断当前的 SQL 语句类型,并转给 SQL 执行器 Executor 去执行。Executor 先尝试从框架自带的缓存(一级缓存和二级缓存)中获取当前查询对应的结果,如果缓存不命中则会执行数据库操作。对于查询操作而言,数据库返回的结果集与我们期望的实体对象之间还差那么一丢丢,此时,MyBatis 强大的结果集映射处理就可以大显身手,将结果集按照我们的配置映射成为具体的实体类对象(集合)返回。

以上只是大致对框架的运行机制做了一个概括,具体的实现细节后续会用专门的文章进行讲解。下面来看一下 MyBatis 整体的架构设计,如下图所示:

image

按照功能进行划分,并参考前人的一些总结,我将 MyBatis 的架构设计分为三层:基础支持层、核心处理层,以及对外接口层。本系列后续的文章中将分别从配置文件解析、映射文件解析,以及 SQL 语句执行机制三个方面对 MyBatis 的实现进行探究,期间会涉及到上图中的各个模块。拟定博文标题如下:

  1. MyBatis 源码解析:配置文件的加载与解析
  2. MyBatis 源码解析:映射文件的加载与解析
  3. MyBatis 源码解析:SQL 语句执行机制

上车,走啦~