本文详细介绍了阅读MyBatis源码的学习思路,并且对源码做了详细注释,讲解了整个Mybatis的启动过程。
本文篇幅较长,所以先来一张 PLMM 让大脑兴奋下,建议收藏阅读,非常适合用于面试前的重点复习。大脑兴奋了吧,接下来吃偷 Mybatis。
为什么学?如何学?
为什么要阅读MyBatis源码?因为作为当前最热门的经典ORM框架,在所有框架里Mybatis源码可以说是最简单易懂的,非常适合上手阅读,而且Mybatis也是面试中常问的知识点,必须要掌握。
如何去学习MyBatis源码呢?
所有框架和技术的学习都一样,按照"大胆假设,小心求证"的思路,先问自己几个问题。
在MyBatis出现之前软件开发中存在什么问题?怎么解决这个问题?如果是你怎么做?别人(MyBatis)怎么做?
JDBC 执行流程
因为所有的ORM框架其根本都需要依赖于JDBC与MySQL服务器进行交互,所以再次回顾一下JDBC的整个执行流程。
public static void main(String[] args) throws Exception {
//1. 加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
//3. 操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM emp");
while(rs.next()){
System.out.println(rs.getString("name")+" -"+rs.getInt("age"));
}
}
从上述JDBC的demo可以看出,JDBC执行主要有以下核心流程。
-
加载驱动程序
-
连接数据源,建立数据库连接connection。
-
准备SQL,建立statement
-
向数据库发送命令,执行SQL。
-
处理数据库响应并返回结果。
-
关闭资源。
存在了什么问题?
通常来说,一个框架就是解决了两个问题。一是降低代码冗余度,降低使用难度;二是解耦,让开发者更专注于业务开发。
目前在项目生产中,直接使用到JDBC的时候大家可以明显感觉到存在几个问题:
-
JDBC的API使用繁琐,接口使用难度高,方法不便于复用,对业务侵入度高。
-
数据库的配置信息难以维护。
-
对于表结构和Java对象的关系难以维护。
-
编写原生SQL繁琐容易出错且不安全,难以复用。
我会如何设计?
类似的,如果我去实现Mybatis 如何去做呢?核心步骤一定脱离不开JDBC,这是一切ORM框架和数据库操作的基础,所以扩展应该基于此展开。
既然需要解决配置繁琐的问题,那就需要将配置信息封装到一起;JDBC使用复杂,那就将JDBC接口做更高一层的封装;Java对象和表结构的关系难以维护那就采用ORM思想增加对应配置,作为实体与表结构的映射;SQL难以复用和编写,那就将SQL独立出来配置,让开发人员专注于SQL的编写和优化。
总结来说如果需要解耦合,那就增加一个中间层,需要灵活复用,就增加映射配置。
所以我们可以推断MyBatis的流程如下:
-
配置实体关系映射文件,以及对应SQL配置映射文件,配置数据库配置文件。
-
读取配置,并且解析,将配置实例化为对象。
-
通过配置信息建立transaction,建立statement。
-
对于SQL的执行流程,因为对于SQL的解析逻辑和流程是一样的,可以用动态代理模式+模版模式生成执行类,代理执行。
-
替换SQL中的参数,生成实际SQL,执行SQL
-
拿到执行结果,通过映射文件解析结果,并且生成Java对象。
1-3是启动流程,4-5是每次的执行流程。
MyBatis 核心流程如何做?
Mybatis的核心流程氛围两个阶段,启动准备阶段和执行SQL阶段。
-
加载配置XMl文件。
-
读取mybatis的dtd描述文件,并且解析xml标签
-
通过读取的XMl配置信息生成对应的全局配置对象,以及生成mapper不同方法的SQL映射。
-
创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。
-
根据方法签名,获得mapper对应的代理对象。
-
通过JDK动态代理找到实现类,获得数据库连接,然后执行SQL。
-
拿到执行结果,并且根据映射关系处理为Java对象。
-
执行结束,关闭资源。
其中重点在于5个点:解析xml、生产代理对象、获得代理对象、执行SQL、处理结果集。
源码调试用例
首先准备数据库数据:
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT '',
`age` int(11) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into user_info values(1,'后端开发技术',20,'2022-06-14 12:00:00');
建立测试用例,看一下如何手动启动MyBatis,以下代码依据官网给出的用例进行了改造。具体代码不完整贴出来了,比较简单。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 根据全局配置文件创建出SqlSessionFactory
// SqlSessionFactory:负责创建SqlSession对象的工厂
// SqlSession:表示跟数据库建议的一次会话
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// =================================================================
// 获取数据库的会话,创建出数据库连接的会话对象(事务工厂,事务对象,执行器,如果有插件的话会进行插件的解析)
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取要调用的接口类,创建出对应的mapper的动态代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
// 调用方法开始执行
User userInfo = mapper.findUserInfo(1);
上半部分是启动和准备过程,下半部分是执行SQL过程。
启动准备阶段流程
这个阶段我们重点解读以下流程:
-
加载配置XMl文件
-
读取mybatis的dtd描述文件,并且解析xml标签
-
通过读取的XMl配置信息生成对应的全局配置对象