ORM框架是几乎所有Java应用都会用到的技术,大多数ORM框架都是将Java对象与数据表进行关联,Mybatis则是将Java方法和SQL语句进行了映射(Mapping)。这样不仅简化了ORM操作,同时也支持数据表关联查询、视图查询、存储过程调用等比较复杂的操作。本文就来分析一下MyBatis的工作原理,这也是Java面试中经常考察的内容。

Mybatis实现了方法与SQL语句之间的映射
MyBatis的操作主要有两个阶段:
- 初始化阶段: 完成MyBatis运行环境的准备工作,包括读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作。
- 数据读写阶段: 由数据读写操作触发,按照要求完成具体的CRUD操作。
MyBatis初始化
在初始化阶段,MyBatis 主要进行了以下几项工作。
- 首先根据配置文件的位置,获取它的输入流 InputStream。通常MyBatis的配置文件是XML文件。
- 从配置文件的根节点开始,逐层解析配置文件,也包括相关的映射文件。解析过程中不断将解析结果放入 Configuration对象。
- 以配置好的 Configuration对象为参数,获取一个 SqlSessionFactory对象,也就是 SqlSession 的工厂实例。

SqlSessionFactoryBuilder的build()方法
在 org.apache.ibatis.session 包中定义的 SqlSessionFactoryBuilder 的 build() 方法,可以接收 Configuration 对象为参数,并创建后返回 SqlSessionFactory 对象。
MyBatis数据库操作
在初始化阶段结束之后,我们已经获得了 SqlSessionFactory 工厂实例,数据库的操作需要一个 SqlSession 对象。
SqlSession对象是程序与数据库进行交付的桥梁,主要作用包括:向SQL语句传入参数,执行SQL语句,获取执行SQL语句的结果,以及事务的控制。
当进行一次数据库的读或写操作时,MyBatis要经过的主要步骤。
(1)首先通过 SqlSessionFactory 的 openSession() 方法获得 SqlSession 实例。其实现细节都在在 DefaultSqlSessionFactory 的 openSessionFromDataSource() 方法中。

DefaultSqlSessionFactory的openSessionFromDataSource()方法
这个方法利用Configuratoin中的配置信息创建了事务工厂 TransactionFactory、执行器 Executor,并返回了默认 DefaultSqlSession。
(2)在查询数据前,要先实现映射接口文件(xxMapper.class)与映射文件(xxMapper.xml)的绑定,通过SqlSession对象的 getMapper() 方法找到接口对应的实现。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

getMapper的实现细节在MapperRegistry类的 getMapper() 方法中。这个方法从所有已经解析的映射文件中找到接口对应的映射文件,然后根据映射文件,利用MapperProxyFactory实例的 newInstance() 方法创建一个接口的实现对象,该方法返回的是一个基于反射的动态代理对象,即 MapperProxy 对象。

MapperProxyFactory的newInstance()方法
(3)当执行接口方法时,会调用代理类 MapperProxy 的 invoke() 方法,并最终调用 MapperMethod 类的 execute() 方法来执行具体的数据库操作,并得到最终的结果。

代理类MapperProxy调用MapperMethod方法
cachedInvoker() 方法负责创建MapperMethod实例。**MapperMethod **是Mybatis框架的核心组件,主要的功能就是执行SQL的相关操作。

MapperMethod类的excute()方法(部分代码)
(4)从数据库中查询结果。execute()方法根据执行的SQL语句类型选择对应的操作,都调用了SqlSession接口对应的方法。以SELECT操作为例,调用了DefaultSqlSession的 selectList() 方法,并由执行器的 query() 执行查询。

在执行查询时,首先尝试从缓存中查找操作结果,如果找到了则返回;如果找不到则从数据库中查询,查询后的结果也放入缓存中。

最终数据库查询结果交给ResultSetHandler对象处理
Executor的 query() 方法经过一系列方法调用,对SQL语句进行了层层转化,并调用jdbc的底层方法执行操作,最终数据库查询得到的结果交给 ResultSetHandler 对象处理。

执行器的query()方法经过层层调用
(5)ResultSetHandler对象的 handleResultSets() 负责处理查询得到的结果集,方法调用链如下图所示。

主要的方法有三个:
- **createResultObject() **负责创建输出结果对象。
- applyAutomaticMappings() 在自动属性映射开启情况下,负责将数据记录的值赋给输出结果对象。
- applyPropertyMappings() 按照用户的映射设置,给输出结果对象的属性赋值,实现逻辑与 applyAutomaticMappings() 方法类似。
最终,载有对象列表的 multipleResults 被返回,程序拿到了查询结果。Mybatis的工作也就完成了。
通过以上步骤可以看出,MyBatis完成一次数据库操作的过程还是十分复杂的。因此,在程序设计中要尽量减少数据库IO操作,这样可以大幅提高软件的运行效率。
参考资料:
【1】https://github.com/mybatis/mybatis-3
【2】《MyBatis源码详解》
我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法
网友评论