暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MyBatis源码剖析(一)

彼岸cheng 2022-04-27
327

在看源码之前,首先来了解一下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 的整体架构了!

文章转载自彼岸cheng,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论