摘要

mybatis的核心流程,主要是对于主线的一个探索。目的是对于整个mybatis流程有个初步的印象

核心流程

核心流程搞懂:主线,涉及的模块不深究。再去基础支持层,再回来核心。

    /**
* MyBatis API 的使用
* MyBatis 在启动的时候会做哪些操作?
* 1.加载全局配置文件
* 2.加载映射文件
* 3.加载的内容存储在了那个Java对象中? Configuration
* @throws Exception
*/
@Test
public void test1() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");
for (User item: list
) {
System.out.println(item.toString());
}
// 5.关闭会话
sqlSession.close(); // 关闭session 清空一级缓存
}

mybatis在启动的时候做什么操作?

  1. 加载全局配置文件
  2. 加载映射文件
  3. 加载的内容存在什么java对象中 ---Configuration

下面的题目分别对应着代码中的四步来进行(第五步是关闭)

获取配置文件

// 1.使用的时候获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");

这一步调用了io包下的Resources类的静态方法getResourceAsStream,里面调用了一个重载的getResourceAsStream方法,两个参数是类加载器和路径,其中类加载器为空。

  /**
* io.Resources
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}

而后还是重载嵌套,其中的getClassLoaders加载了五种加载器,封装到一个类加载器数组中

  public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}

而后通过classLoaderWrapper中的getResourceAsStream生成字节输入流。

/**
* io.ClassLoaderWrapper
* Try to get a resource from a group of classloaders
*
* @param resource - the resource to get
* @param classLoader - the classloaders to examine
* @return the resource or null
*/
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) { // 不为空就加载 // try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource); // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
} if (null != returnValue) {
return returnValue; // 猜测是变量放到里面节省内存空间?
}
}
}
return null;
}

这样就拿到了字节流。

加载解析配置文件

    // 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

首先是SqlSessionFactory,可以看到他是一个接口,里面有若干的方法,比如selectOne等。而他的实现有两个,分别是 DefaultSqlSessionFactory SqlSessionManager 。而我们没有直接通过DefaultSqlSessionFactory直接获取,而是通过Builder(建造者)模式进行获取。

为什么通过建造者获取呢?
1. SqlSessionFactory的目的是生产SqlSession的,当我们系统启动之后我们要获取工厂对象的时候,SqlSessionFactory工厂对象应该是单例。
2. 全局配置文件和映射文件也只需用在系统启动的时候完成加载操作。
由于1、2点,我们可以直接在工厂中直接完成配置文件的加载解析和工厂的创建。
但是这样会导致我们的职责不单一,所以用建造者模式来进行拆分。构建出这个复杂的对象。

SqlSessionFactoryBuilder的build方法重载了build方法,变成三个变量InputStream,String类型的环境,Properties三个变量,后两个为空。具体的解析给到了XMLConfigBuilder里。

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最终返回一个 DefaultSqlSessionFactory >>
// 全局配置文件中的信息都被封装到了Configuration对象中
// 映射文件中的配置信息同样的也被封装到了Configuration对象中
// 一个具体的CRUD标签的信息被封装到MappedStatment对象中
return build(parser.parse());// 调用的下面的build
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 这里暴露了一个事实,信息是放到Configuration中的。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

然后通过XMLConfigBuilder把字节流转为了XMLConfigBuilder对象(暂时不看),只需要知道他继承自BaseBuilder,在里面新建了一个Configuration实例,Configuration构造函数对配置进行了初始化,而后调用其中的parse方法,进行加载解析。保证只加载一遍,而后解析。

  public Configuration parse() {
// 保证加载解析只能做一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

再往后就是parseConfiguration,里面的参数XNode就是对应着我们的mapper-config.xml的内容,在里面的dtd文件中可以看到他们规定规定内容。

  private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 对于全局配置文件各种标签的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 读取文件
loadCustomVfs(settings);
// 日志设置
loadCustomLogImpl(settings);
// 类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于创建对象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于对对象进行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子标签赋值,默认值就是在这里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 创建了数据源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

以propertiesElement举例,可以看到他的作用是把提取到的信息放到Configuration中的variables中去。

  private void propertiesElement(XNode context) throws Exception {
if (context != null) { // 意味着这个属性可以没有
// 创建了一个 Properties 对象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// System.out.println(resource+": adsf");
// System.out.println(url +": adsf");
if (resource != null && url != null) {
// url 和 resource 不能同时存在
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
// 加载resource或者url属性中指定的 properties 文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
// 和 Configuration中的 variables 属性合并
defaults.putAll(vars);
} // for (Map.Entry item: defaults.entrySet()
// ) {
// System.out.println(item.getKey()+":"+item.getValue());
// }
// 更新对应的属性信息
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}

这样我们就知道了我们解析的配置文件都存到什么地方(Configuration),怎么存的(Mapper)了。

打开session

    // 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();

SqlSessionFactory是一个接口,他的实现类是DefaultSqlSessionFactory。里面实现openSession的方法如下:

  @Override
public SqlSession openSession() { // 执行器才是真和数据库操作,干活的getDefaultExecutorType
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

openSessionFromDataSource代码如下,通过事务控制,创建了一个执行器和一个DefaultSqlSession类的实例。

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据事务工厂和默认的执行器类型,创建执行器 >>
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

执行器executor才是真正的完成数据库的操作。点进去newExecutor,里面负责配置执行器的类型,和二级缓存还有插件。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 默认 SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
// 二级缓存开关,settings 中的 cacheEnabled 默认是 true
// 映射文件中的<cache>标签只是创建了Cache对象,
// cacheEnabled才会真正对Excutor缓存的增强。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 植入插件的逻辑,至此,四大对象已经全部拦截完毕
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

执行sqlSession中的方法

// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("mapper.UserMapper.selectUserList");

这里指定了全路径,然后就属于sqlSession的方法就执行完成了,程序把具体的执行过程交给了执行器executor进行。

  @Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// statement就是我们的全路径名?
MappedStatement ms = configuration.getMappedStatement(statement);
// 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

执行器是一个接口有两个实现方法,其中第一个是BaseExecutor,另一个是CachingExecutor,先看CachingExecutor,因为整体流程是有cache就用cache,没有就是base。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey:什么样的SQL是同一条SQL? >>
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

query具体的执行是看到有缓存就用二级缓存,如果有二级缓存,就直接把缓存中的数据加载进来。如果没有的话,就执行BaseExecutor中的query,也就是查询数据库。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 <cache> 标签决定
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 写入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

一级缓存查询的逻辑:先查询一级缓存,如果一级缓存中没有结果(list为空)就进行真正的数据库查询

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 异常体系之 ErrorContext
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// flushCache="true"时,即使是查询,也清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 防止递归查询重复处理缓存 queryStack代表深度
queryStack++;
// 查询一级缓存
// ResultHandler 和 ResultSetHandler的区别 localCache.getObject(key)是一级缓存中存在的数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 一级缓存也没有的话,真正的查询流程
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
为什么先二级缓存再一级缓存而不是先一级缓存?
二级缓存是进程级别的,一级缓存是会话级别的(session),生命周期长短是不一样的,二级缓存只要服务没重启就在,一级缓存一次会话结束就无了。二级缓存命中的概率是99%,一级缓存命中可能只有1%概率。

MyBatis核心流程的更多相关文章

  1. 深入详解Mybatis的架构原理与6大核心流程

    MyBatis 是 Java 生态中非常著名的一款 ORM 框架,目前在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架. 如果你想要进入一线大厂,能够熟练使用 MyBatis 开发已经 ...

  2. springmvc 运行原理 Spring ioc的实现原理 Mybatis工作流程 spring AOP实现原理

    SpringMVC的工作原理图: SpringMVC流程 . 用户发送请求至前端控制器DispatcherServlet. . DispatcherServlet收到请求调用HandlerMappin ...

  3. MyBatis 核心配置综述之Executor

    目录 MyBatis四大组件之 Executor执行器 Executor的继承结构 Executor创建过程以及源码分析 Executor接口的主要方法 Executor 的现实抽象 上一篇我们对Sq ...

  4. MyBatis 核心配置综述之StatementHandler

    目录 MyBatis 核心配置综述之StatementHandler MyBatis 四大组件之StatementHandler StatementHandler 的基本构成 StatementHan ...

  5. MyBatis 核心配置综述之 ParameterHandler

    目录 ParameterHandler 简介 ParameterHandler 创建 ParameterHandler 中的参数从何而来 ParameterHandler 解析 MyBatis 四大核 ...

  6. Mybatis工作流程及其原理与解析

    Mybatis简介:    MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBat ...

  7. MyBatis运行流程及入门第一个程序

    1. mybatis是什么? MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并 ...

  8. MyBatis——MyBatis开发流程

    创建项目(IDEA中) 在IDEA中创建 MyBatis项目,详细流程如下: 这里有一点需要注意,我们创建的是Maven项目,如果大家以前没有配置过Maven环境的话,在创建完项目之后,需要配置一下M ...

  9. Mybatis基础:Mybatis映射配置文件,Mybatis核心配置文件,Mybatis传统方式开发

    一.Mybatis快速入门 1.1 框架介绍 框架是一款半成品软件,我们可以基于这个半成品软件继续开发,来完成我们个性化的需求! 框架:大工具,我们利用工具,可以快速开发项目 (mybatis也是一个 ...

  10. paip.刮刮卡砸金蛋抽奖概率算法跟核心流程.

    paip.刮刮卡砸金蛋抽奖概率算法跟核心流程. #---抽奖算法需要满足的需求如下: 1 #---抽奖核心流程 1 #---问题???更好的算法 2 #---实际使用的扩展抽奖算法(带奖品送完判断和每 ...

随机推荐

  1. Llama2 论文中译版——开放式基础和微调聊天模型

    Llama 2:开放式基础和微调聊天模型 写在前头 因为最近一直在使用 LLM 工具,所以在学习 Llama 2:开放式基础和微调聊天模型 这篇论文的期间,顺手将内容翻译了过来. 整片译文是由 Cha ...

  2. opencv中的函数

    读入图像:cv2.imread(),第一个参数:未文件路径,第二个参数:告诉函数要以何种方式读取图片. cv2.IMREAD_COLOR:读入一幅彩色图像.图像的透明度会被忽略. cv2.IMREAD ...

  3. 自动刷新服务:nodemon

    安装命令: npm install -g nodemon 运行命令: nodemon server.js 运行结果:

  4. RocketMq消费原理及源码解析

    消费原理概览 先简单说下常见的rocketMq的部署方式,上图中broker为真正计算和存储消息的地方,而nameServer负责维护broker地 图中右侧consume message部分即是本文 ...

  5. SpringCloud-Hystrix服务熔断与降级工作原理&源码

    先附上Hystrix源码图 在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用 ...

  6. [k8s]使用nfs挂载pod的应用日志文件

    前言 某些特殊场景下应用日志无法通过elk.grafana等工具直接查看,需要将日志文件挂载出来再处理.本文以nfs作为远程存储,统一存放pod日志. 系统版本:CentOS 7 x86-64 宿主机 ...

  7. SpringBoot3之Web编程

    标签:Rest.拦截器.swagger.测试; 一.简介 基于web包的依赖,SpringBoot可以快速启动一个web容器,简化项目的开发: 在web开发中又涉及如下几个功能点: 拦截器:可以让接口 ...

  8. vlak

    2023-7-14 题目 luogu题目传送门 题目描述 Nina 和 Emilija 正在玩一个特殊的游戏.这个游戏是在一张最开始为空白的纸上进行的.在每一个人的行动回合内,这个人会在这张纸上当前的 ...

  9. GIT提交修改的项目到远程仓库

    1.在项目目录下右键选择Git Bash. 2.执行提交命令三部曲 git add . //文件-暂存区,即将所有新增的文件添加到提交索引中,,add后面是"空格 点"就表示当前目 ...

  10. JSTL常用代码总结

    1. jstl判空: (1) 须先引人<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix=" ...