摘要

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. 一个可将执行文件打包成Windows服务的.Net开源工具

    Windows服务一种在后台持续运行的程序,它可以在系统启动时自动启动,并在后台执行特定的任务,例如监视文件系统.管理硬件设备.执行定时任务等. 今天推荐一个可将执行文件打包成Windows 服务的工 ...

  2. Java stream 流

    Java stream 流 中间操作 1.filter 作用:将流中的元素,基于自定义的比较器进行去重 方法定义 Stream<T> filter(Predicate<? super ...

  3. torch.nn基础学习教程 | PyTorch nn Basic Tutorial

    基于torch.nn搭建神经网络的基础教程大纲: 1. 引言 在我们开始深入探讨torch.nn之前,我们首先需要理解PyTorch及其神经网络库的基础知识.这一部分的内容将帮助你对PyTorch有一 ...

  4. API接口的技术的概念

    当今互联网技术的发展越来越快,越来越多的网站和应用程序需要获取外部数据来提供更好的服务和用户体验,这就需要使用API接口.本文将会对API接口的概念.类型以及如何调用API接口进行简要介绍. 一.什么 ...

  5. 手把手教你使用Vite构建第一个Vue3项目

    写在前面 在之前的文章中写过"如何创建第一个vue项目",但那篇文章写的是创建vue2的 项目. 传送门如何创建第一个vue项目 打开Vue.js官网:https://cn.vue ...

  6. Confluence的Excel插件Elements Spreadsheet安装

    背景 Confluence是现在广泛使用的团队协作文档系统.虽然自身带了一些表格编辑功能,但表格的整体功能较弱,比如不能通过Excel文件进行导入导出,表格在复制到Excel时格式会比较奇怪等等.对于 ...

  7. 如何在.NET电子表格应用程序中创建流程图

    前言 流程图是一种常用的图形化工具,用于展示过程中事件.决策和操作的顺序和关系.它通过使用不同形状的图标和箭头线条,将任务和步骤按照特定的顺序连接起来,以便清晰地表示一个过程的执行流程. 在企业环境中 ...

  8. Solution -「BZOJ 3771」Triple

    Description Link. 给你一个序列,你每次可以取 \(1\sim3\) 个数然后计算和,问你对于每一种和,方案数是多少. Solution 设一个 OGF \(A(x)=\sum_{i= ...

  9. vi命令使用详解

    vi命令使用详解 1. 三种工作模式 命令模式:通过命令对文件进行常规操作 打开文件时进入命令模式 (vi的入口) 通过命令对文件进行常规操作,如定位.翻页.复制.粘贴.删除等在图形界面下通过鼠标或快 ...

  10. Mac上虚拟环境的安装与使用

    Mac上虚拟环境的安装与使用 介绍 virtualenv是python虚拟环境,能够和系统环境相隔离,保持环境的纯净. virtualenvwrapper可以方便管理虚拟环境 安装 pip insta ...