前言
返璞归真,今天看下mybatis的执行流程。
先来案例
一、首先是配置文件,需要一个xml的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases> <!--配置实体类的别名-->
<package name="fast.cloud.nacos.mybatis.entity"/>
</typeAliases>
<environments default="MySQLDevelopment">
<environment id="MySQLDevelopment">
<transactionManager type="JDBC"></transactionManager> <!--事务-->
<dataSource type="POOLED"> <!--数据库连接-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="fast.cloud.nacos.mybatis.mapper"/>
</mappers>
</configuration>
二、定义
Mapper
和entity
,详细代码在https://github.com/fafeidou/fast-cloud-nacos/tree/master/fast-source-code-analysis/code-mybatis
三、查询,如下所示
@Test
public void testMybatis() throws IOException {
//1. 加载核心配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 解析核心配置文件并创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//3. 创建核心对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//4. 得到Mapper代理对象
DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);
//5. 调用自定义的方法实现查询功能
List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept();
for (DeptEmp de : list) {
System.out.println(de);
}
//6. 关闭sqlSession
sqlSession.close();
}
逐步分析
我想着是以打断点的方式,逐步去看
mybatis
的orm
- 解析
MyBatis
核心配置文件并创建SqlSessionFactory
对象
//1. 加载核心配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 解析核心配置文件并创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
这三行代码先是创建了SqlSessionFactoryBuilder 对象,然后获得了MyBatis 核心配置文件的输入流,最后调用了build 方法,我们接下来跟踪一下该方法的源码。
第77 行代码创建了一个xml 解析器对象,并在第78 行对MyBatis 核心配置文件进行了解析,拿到了数据库连接数据以及映射配置文件中的数据(包括我们编写的sql 语句和自定义的resultMap),XMLConfigBuilder
的parse
方法,将xml的配置解析成java
对象。
XMLConfigBuilder#environmentsElement
设置数据源和事务管理器
观察configuration
对象,这个时候可看到已经初始化好数据源和扫描到的mapper
。
还有mapper
里面的方法
还有resultMap
映射
在DefaultSqlSessionFactory 的构造方法中,我们发现,解析后拿到的数据最终被全部存入到了一个名字为Configuration 的对象中,后续很多操作都会从该对象中获取数据。
SqlSession sqlSession = sqlSessionFactory.openSession();
图中红框部分代码,主要通过读取Configuration 对象中的数据分别创建了Environment 对象,事务对象,Executor 对象等,并最终直接new 了一个DefaultSqlSession 对象(SqlSession 接口的实现类),该对象是MyBatis API 的核心对象。
DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);
最终调用了MapperRegistry
对象的getMapper
方法,把实体类和sqlSession
对象传了过去。
第50 行代码通过代理工厂最终把我们自定义的Mapper 接口的代理对象创建了出来。
List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept();
当我们调用代理对象的getEmpTotalByDept()方法时,框架内部会调用MapperProxy 的invoke方法, 我们可以观察一下这个方法三个参数的值,如下图所示
在invoke 方法内锁定第53行代码,该行代码调用MapperMethod 的execute 方法实现查询功能。
在execute 方法内先进行增删改查判断,本案例进行的是查询,并且可能会查询出多条记录,所以最后锁定第68 行代码,该行代码调executeForMany 方法进行查询。
第124 行代码通过hasRowBounds()方法判断是否进行分页查询,本案例不需要,所以执行else 中的代码,调用sqlSession 的selectList 方法(第128 行), 参数的值如下图所示:
第147 行代码从Configuration 对象中获得MappedStatement 对象,该对象存储了映射配置文件中的所有信息(包括我们编写的sql 语句和自定义的resultMap),如下图所示:
第124 行代码调用了CachingExecutor 的query(…)方法, 继续往下跟踪。
第81 行通过ms 对象从mapper 映射配置文件中获得了sql 语句,如下图所示,第93 行代码调用了下面的query 方法,该方法内部先从缓存中取数据,如果缓存中没数据,就重新查询(第87 行),继续往下跟踪。
缓存没有数据就会查询BaseExecutor#query的queryFromDatabase这个方法,就是从数据库中获取数据。
最终执行了sql 语句并将查询结果按照我们自定义的resultMap 进行了封装,结果如下图所示