前言
完成了一个User表的CRUD
了解了mybatis-config.xml的配置文件标签
学会了使用日志查看sql执行过程
现在,需要仔细分析Mybatis的执行流程
执行流程
其实,从我们前面的工具类大致可以看出一下步骤
Resources ——》InputStream(解析xml)——》SqlSessionFactoryBuilder().build(inputStream)——》SqlSessionFactory——》SqlSession——》动态代理Mapper接口
package com.learn.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//创建能执行sql命令的对象SqlSession
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
实际上,分析源码可以看出具体步骤:
-
resources通过io获得mybatis-config.xml文件,解析成InputStream流
-
new一个SqlSessionFactoryBuilder(建造者),调用build方法,把流中的配置文件数据读取到XMLConfigBuilder
-
XMLConfigBuilder调用parse方法解析配置文件,它把配置文件中的各种标签属性全部解析获得数据,注入configuration对象存储数据
-
返回的configuration对象,回到SqlSessionFactoryBuilder的build方法,作为参数传入DefaultSqlSessionFactory并建造该对象,这个就是我们得到的SqlSessionFactory
-
SqlSessionFactory.openSession()
调用openSessionFromDataSource方法,该方法通过configuration获得配置文件中的环境标签,然后创建了Environment、TransactionFactory、Transaction、Executor对象,并返回了DefaultSqlSession -
我们在测试类中使用的SqlSession就是这里返回的DefaultSqlSession,这里就进入到了测试类了
-
sqlSession.getMapper(UserMapper.class)
是把Mapper.class传入DefaultSqlSession,而DefaultSqlSession返回的是configuration.getMapper(type, this)
,configuration的方法又是返回mapperRegistry.getMapper(type, sqlSession);
mapperRegistry类中使用动态代理帮mapper接口生成代理实现类 -
上面得到的代理对象就是结果,通过返回的代理对象执行mapper中的getUserList方法,其实走的是MapperProxy的invoke方法,这个方法最终返回
mapperMethod.execute(sqlSession, args);
-
进入MapperMethod类中的execute方法是判断sql的执行类型,例如我们现在是select方法,通过判断进行sqlSession.select方法,又进入了DefaultSqlSession,这个方法最终执行的是executor.query
-
这里就有一定的顺序,先往CachingExecutor(二级缓存)查找,如果二级缓存没有数据,就进入delegate.query方法(一级缓存),如果一级缓存也没有,就查询数据库,并将数据库的数据放入一级缓存
(一级缓存PerpetualCache其实是个HashMap) -
再深追就是JDBC的内容了,最终得到了结果,返回结果集
这些都可以通过源码一步一步查询到
大致的流程图
后续开始源码分析Mybatis的执行流程
Resources
Resources类属于org.apache.ibatis.io包下,返回的是InputStream
可以看出,通过classLoaderWrapper类加载器的封装类,解析xml地址,来把mybatis-config.xml解析成InputStream流
SqlSessionFactoryBuilder
new一个SqlSessionFactoryBuilder,调用build方法,把前面的配置文件流放入
其实有很多build方法,可以对应reader流或者Properties配置文件、environment
指向3个参数的build方法,该方法内创建了XMLConfigBuilder对象
并调用了XMLConfigBuilder.parse方法解析配置文件流中的配置文件属性
最终调用参数是配置对象的build方法
传入配置对象即可返回DefaultSqlSessionFactory
XMLConfigBuilder
前面第一下是调用这个parse方法
evalNode是XPathParser类的方法
XPath是解析XML文件的解析方式,不清楚的可以看一下真的了解XML吗? - XML解析方式
我们清楚了通过这个方法解析了Mybatis-config.xml
然后调用parseConfiguration方法
看这一堆标签,不就是前面学的Mybatis核心配置文件的标签吗?
这个方法中包含了很多对应标签的方法,都是将标签内的属性注入属性对象configuration中
执行完这个方法后,把解析完xml并注入完属性的属性对象configuration返回
回到了SqlSessionFactoryBuilder,执行参数为配置文件的方法
返回DefaultSqlSessionFactory
SqlSession
DefaultSqlSessionFactory是工厂类,通过openSession创建了SqlSession(其实实现类是DefaultSqlSession)
前面分析了,在DefaultSqlSessionFactory中保存着配置属性对象
调用了openSessionFromDataSource方法(参数通过属性对象得到了执行器类型)
openSessionFromDataSource里创建了几个重要的对象:
- Environment环境对象(配置文件里的Environments标签)
- TransactionFactory事务工厂对象(创建事务对象)
- Executor执行器(MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护)
- 返回DefaultSqlSession
经过层层处理,最终返回了DefaultSqlSession(configuration, executor, autoCommit)
它带着属性对象configuration,执行器executor和boolean值判断是否自动提交
数据库的会话和事务控制放到了SqlSession对象中
getMapper
前面已经得到了DefaultSqlSession,接下来就是测试类的内容
sqlSession.getMapper方法,这是什么呢?
其实从它需要UserMapper的class文件就可以猜到使用的反射的方法,看看源码
我们查看DefaultSqlSession的getMapper方法:
它调用了configuration的getMapper方法(参数是mapper的class文件和sqlSession)
进行调用:
MapperProxyFactory、newInstance方法这不就是反射吗
HashMap,用于存储配置文件中的mappers标签的每个mapper接口
newInstance方法,帮mapper接口生成代理实现类
Proxy.newProxyInstance就是动态代理
不是很清楚的可以学习一下:设计模式(15)结构型模式 - 代理模式:静态、动态代理、Cglib代理
这种设计是代理模式的动态代理,当然还需要反射的知识
生成MapperProxy这个真正的事件处理对象
getUserList
获得了mapper接口的代理实现类,接下来就是进入具体的实现方法
动态代理就是代理对象的invoke方法代替Mapper接口的getUserList方法
真正进入的是MapperProxy的invoke方法
mapperMethod.execute方法判断sql的执行类型(把sqlSession作为参数)
根据不同的sql类型,执行不同的方法,我们现在是select方法,就查看select块:
又回到了DefaultSqlSession,执行select方法
调用了执行executor的query方法
executor执行器的结构是这样的:
走CachingExecutor的query方法,先从二级缓存中获取
这里有一个重要的对象BoundSql
getBoundSql负责获取绑定的sql命令,比如"SELECT * FROM xxx",赋值给BoundSql,创建cacheKey
然后进入另一个query方法
如果二级缓存不存在数据,delegate.query,即进入一级缓存查询数据
执行query操作的是SimplyExecutor代理来完成的,接着就进入到了SimplyExecutor的父类BaseExecutor的query方法中
localCache是一级缓存,localCache本质上是一个HashMap
如果是第一次执行查询操作(一级缓存也没有数据)
就调用queryFromDatabase方法去数据库查询
doQuery是抽象方法,所以进入子类SimplyExecutor,进行数据库查询
configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
根据既有的参数,创建StatementHandler对象来执行查询操作
SQL查询参数的设置prepareStatement(handler, ms.getStatementLog());
,也就是咱们最开始传递进来的参数
handler.query(stmt)
方法中会进行实际的SQL查询操作和结果集的封装(封装成Java对象)
后续程序就是深入到SQL查询及结果集的设置中,然后是SQL查询操作和结果集的封装:可以看这篇文章SQL语句执行的完整流程
总结
准备工作:
Resource对象把对应的XML文件加载,通过xmlConfigBulider解析,封装到Configuration对象,作为参数传递到SqlSessionFactory.build方法建造DefaultSqlSessionFactory,DefaultSqlSessionFactory的openSession创建DefaultSqlSession,带上了transaction和executor用于后续执行操作(数据库的会话和事务控制放到了SqlSession对象中)
具体测试类:
DefaultSqlSession动态代理Mapper接口,生成代理对象MapperProxy,执行对应的方法,真正执行的是代理对象的invoke方法,该方法最终使用到executor执行器,先查看二级缓存是否存在数据,然后查看一级缓存,如果没有就通过StatementHandler进行数据库查询操作,query方法进行结果集封装
最终返回查询结果(list)
Mybatis中执行流程大致就是这样,源码还有很多没有分析的,如后续的sql查询、结果集处理,还有很多细节,留到以后再去分析
学海无涯苦作舟
都看到这了,点个赞呗(^_−)☆