在看源码之前,首先来了解一下MyBatis 的整体架构。
MyBatis 分为三层架构,分别是基础支持层、核心处理层和接口层,如下图所示:
1. 基础支持层
基础支持层是整个 MyBatis 框架的地基,为整个 MyBatis 框架提供了非常基础的功能。
(1)类型转换模块
对应源码中的 type 包
在 MyBatis 核心配置文件(mybatis-config.xml)中,可以通过 <typeAliase> 标签设置一个类或者一个包中的所有类的别名,这里面的别名机制,就是类型转换模块的主要功能之一。
另一个主要功能是在 mapper 文件中,实现 JDBC 类型与 Java 类型之间 的转换,该功能在为SQL进行绑定实参以及映射ResultSet场景中都会有所体现:
在 SQL 绑定用户传入的实参时,类型转换模块会将 Java 类型数据转换成 JDBC 类型数据;
在将 ResultSet 映射成结果集的时候,类型转换模块会将 JDBC 类型数据转换成 Java 类型数据。
(2)日志模块
对应源码中的 logging 包
不管在任何的环境中,日志在整个系统中的地位都是非常重要的。日志可以帮助我们在环境中排查问题原因、定位Bug,也可以帮助运维人员定位性能瓶颈等问题。MyBatis也不会例外。在官方文档中,可以使用多种日志框架,例如 Log4j、 Log4j2、Slf4j 等。
日志模块的一个主要功能就是集成第三方日志框架。
(3)反射模块
对应源码中的 reflection 包
Java中的反射功能强大,但是对于大多数程序员来说在实际的工作中很少接触反射技术;接触到的开发人员也很难写出高质量的反射代码。在 MyBatis 中专门提供了反射模块,该模块是在Java反射的基础上进行的良好封装,提供了更加灵活、简洁易用的 API 接口,同时缓存 Java 的原生反射相关的元数据,提升了反射代码执行的效率,优化了反射操作的性能。
(4)Binding 模块
对应源码中的 binding 包
在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL 节点。如果出现拼写错误,就只能在运行时才能发现相应的异常。为了尽早发现这种错误,MyBatis 通过 Binding 模块,将用户自定义的 Mapper 接口与映射配置文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。
特别说明:在使用 MyBatis 的时候,开发人员无须编写自定义 Mapper 接口的实现,MyBatis 会自动为其创建动态代理对象。有些简单的数据操作,我们还可以直接在 Mapper 接口中使用注解完成,连 Mapper.xml 配置文件都无须编写,但如果 ResultSet 映射以及动态 SQL 非常复杂,还是建议在 Mapper.xml 配置文件中维护会比较方便,例如动态 SQL 语句的定义。
(5)数据源模块
对应源码中的 datasource 包
持久层框架核心组件之一就是数据源,一款性能出众的数据源可以成倍提升系统的性能。MyBatis 自身提供了一套不错的数据源实现,也是 MyBatis 的默认实现。另外在 Java 生态中,就有很多优异开源的数据源可供选择,MyBatis 的数据源模块中也提供了与第三方数据源集成的相关接口,这也为用户提供了更多的选择空间,提升了数据源切换的灵活性。
(6)缓存模块
对应源码中的 cache 包
现在很多业务数据都会落地到数据库,所以数据库性能的优劣直接影响了上层业务系统的优劣。很多线上业务都是读多写少的场景,在数据库遇到瓶颈时,缓存是最有效、最常用的手段之一,正确使用缓存可以将一部分数据库请求拦截在缓存这一层,这就能够减少一部分数据库的压力,提高系统性能。
MyBatis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个 JVM 中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用 Redis、Memcache 等缓存产品。
(7)解析器模块
对应源码中的 parsing 包
在 MyBatis 中有两大配置文件,一个核心配置文件 mybatis-config.xml ,一个是 Mapper.xml 配置文件,这两个文件都是由MyBatis的解析器模块进行解析的。
解析器模块主要提供两大功能:
(1)对 XPath 进行封装,实现对xml配置文件的解析工作;
(2)为处理动态SQL语句中的占位符提供支持;
(8)事务管理模块
对应源码中的 transaction 包
持久层框架一般都会提供一套事务管理机制实现数据库的事务控制,MyBatis 对数据库中的事务进行了一层简单的抽象,提供了简单易用的事务接口和实现。
在很多场景中,MyBatis 会与 Spring 框架集成,并由 Spring 框架管理事务。
(9)资源加载模块
对应源码中的 IO 包
资源加载模块,主要是对类加载器进行封装,确定类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能 。
2. 核心处理层
核心处理层是 MyBatis 核心实现所在,其中包括 MyBatis 的初始化以及完成一次数据库操作的涉及的全部流程 。
(1)配置解析
对应源码中的 builder 和 mapping 包,前者为配置解析过程,后者主要为 SQL 操作解析后的映射
在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、Mapper.xml 映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。例如:
<resultMap> (即 ResultSet 的映射规则) 会被解析成 ResultMap 对象。
<result> (即属性映射)会被解析成 ResultMapping 对象。
之后,可以利用该 Configuration 对象创建 SqlSessionFactory 对象(也就是创建 SqlSession 对象的工厂对象)。等到 MyBatis 初始化之后,开发人员可以通过初始化得到 SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。
(2)SQL 解析
对应源码中的 scripting 包
MyBatis 中的 scripting 模块就是负责动态生成 SQL 的核心模块。它会根据运行时用户传入的实参,解析映射文件中定义的动态 SQL 中的标签,并形成 SQL 模板,然后处理 SQL 模板中的占位符,用运行时的实参填充占位符,得到数据库真正可执行的 SQL 语句。
(3)SQL 执行
对应源码中的 executor 和 cursor 包,前者对应执行器,后者对应执行结果的游标。
在 MyBatis 中,要执行一条 SQL 语句,会涉及非常多的组件,其中比较核心的有:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。
其中,Executor 会调用事务管理模块实现事务的相关控制,同时会通过缓存模块管理一级缓存和二级缓存。SQL 语句的执行将会委托给 StatementHandler 实现。StatementHandler 会先依赖 ParameterHandler 进行 SQL 模板的实参绑定,然后由 java.sql.Statement 对象将 SQL 语句以及绑定好的实参传到数据库执行,从数据库中拿到 ResultSet,最后,由 ResultSetHandler 将 ResultSet 映射成 Java 对象返回给调用方。
整体执行一条SQL语句的过程如下:
(4)插件
对应源码中的 plugin 包
很多成熟的开源框架,都会以各种方式提供扩展能力。当框架原生能力不能满足某些场景的时候,就可以针对这些场景实现一些插件来满足需求,这样的框架才能有足够的生命力。这也是 MyBatis 插件接口存在的意义。我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展。用户自定义插件也可以改变 MyBatis 的默认行为,例如,我们可以拦截 SQL 语句并对其进行重写。
但是由于用户自定义插件会影响 MyBatis 的默认行为,所以在自定义插件之前,必须非常了解 MyBatis 内部的运行原理,以避免写出不符合预期的插件,引入一些诡异的功能 Bug 或性能问题。
3. 接口层
对应源码中的 session 包
接口层是 MyBatis 暴露给调用的接口集合,这些接口都是使用 MyBatis 时最常用的一些接口,例如,SqlSession 接口、SqlSessionFactory 接口等。其中,最核心的是 SqlSession 接口,你可以通过它实现很多功能,例如,获取 Mapper 代理、执行 SQL 语句、控制事务开关等。
这就是 MyBatis 的整体架构了!