从最简单的查询开始理解Mybatis源码

mybatis 官网:mybatis.org/mybatis-3/z...

因为 mybatis 的知识很基础 又比较繁杂,从配置文件的加载到 Mapper.xml 的配置 到命名空间 、二级缓存 ... 虽然使用起来很简单 ,但要细究 里面还是有很多东西的,今天就从最简单的查询开始去洞见 Mybatis 的一些基本概念

首先要准备好:mybaits-config.xml 、SysUserMapper.xml 、SysUser 、SysUserMapper 这几个基础的文件 ,SysUserMapper 里面只需要一个基本的 selectAll 方法即可。

以下是 mybatis 最基本的入门代码:

java 复制代码
String resource = "mybatis-config.xml";  
  
// 1. 加载配置文件  
try( InputStream inputStream = Resources.getResourceAsStream(resource)){  
  
	// 2. 构建 sqlSessionFactory    
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
	  
	// 3.打开 sqlSession  
	try(SqlSession sqlSession = sqlSessionFactory.openSession()){  
		// 4.通过类型获取 mapper  
		SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);  
		 // 5.执行方法
		List<SysUser> sysUsers = sysUserMapper.selectAll();  
		  
		System.out.println(sysUsers);  
	}  
}

通过以上5个步骤即可实现一个 Mapper 的查询操作,下面来跟着这五个步骤来逐一的窥探一下 mybatis 在里面都干了些什么。

1. 加载配置文件

InputStream inputStream = Resources.getResourceAsStream(resource))

一般来说 mybatis-config.xml 都是放在 resource 目录下的,但 mybatis 是如何通过名字找到这个文件的呢? 带着问题,我们直接进入 getResoutceAsStream 方法,直到:org.apache.ibatis.io.ClassLoaderWrapper#getResourceAsStream(java.lang.String, java.lang.ClassLoader) 接下来我们来详解一下 getResourceAsStream(resource, getClassLoaders(classLoader)); 方法

  • getClassLoaders :响应了一个 ClassLoader 数组,包括用户传入的(默认 null ) 的 classLoader 一共返回了 5 个。
  • getResourceAsStream:通过循环遍历 cl 数组,通过 cl.getResourceAsStream(...) 查找 资源。

2. 构建 SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这里通过构建者模式构建了一个 SqlSessionFactory ,看这名字又是一个工厂模式。 那么深入到 build 方法,可以看到最重要的是 新建了 XMLConfigBuilder 对象,通过其 parse 方法 解析了配置文件。

  • 从图中可以看到所有的 mybatis-config.xml 中的节点都有其对应的解析方法,所以当忘记某个配置如何配的时候,除了可以看官网 还可以 看源码来辅助。

其中最重要的就是解析 Mapper 的方法 mapperElement(root.evalNode("mappers")); 通过这个方法可以找到 configuration.addMapper(...) -> mapperRegistry.addMapper(...) mybatis 就是通过这个方法进行的 添加 Mapper 的动作。

可以看到最终 Mapper 被添加到了一个 knownMappers 的 HashMap 中

3. openSession

SqlSession sqlSession = sqlSessionFactory.openSession()

sqlSessionFactory 是构建 sqlSession 的工厂,而 sqlSession 则表示一次查询会话。 默认 mybatis 的 SqlSession 实现是 DefaultSqlSession 最后跳到:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource 方法中

这里有几个比较重要的小知识

  • 在 openSession 中 会传入一个 ExecutorType ( SIMPLE, REUSE, BATCH ) , 表示SqlSession 的执行类型
    • SIMPLE (默认):它为每个语句的执行创建一个新的预处理语句
    • REUSE:这个执行器类型会复用预处理语句
    • BATCH:可批量执行 sql
  • newExecutor 方法中 除了根据类型 New 对应的 Excutor 以外 ,还会判断 cacheEnabled 如果开启了二级缓存,则返回的是 CachingExecutor 这里使用了一个装饰器模式 。

Mybatis 会有 一级缓存和二级缓存

  • 一级缓存(默认有):在一个 session 中执行两次同样的 sql,则会返回上一次缓存的数据。
  • 二级缓存(需配置):跨 session 的,通过装饰器可以组合各种不同的二级缓存,例如:LRU、FIFO
    • org.apache.ibatis.cache.decorators 这里可以看到二级缓存的所有装饰器类(类型)

4.通过类型获取 mapper

SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

getMapper 方法 最终又回到了 MapperRegistry类的 getMapper 方法中

可以看到 从 knownMappers 根据类型获取到了 构建Mapper代理对象的工厂类。(又是一个工厂模式) 通过工厂类创建了一个Mapper的实例,而最终 newInstance 方法响应的就是 MapperProxy<T>

这里插入一个很经典的 mybatis 面试题:为什么 Mybatis 中的 Mapper接口不需要实现类

  • 因为 mybatis 通过 mapperRegistrygetMapper 方法,使用动态代理的方式构建了 Mapper 的实例。在调用 Mapper 的方法时,实际调用的事 MapperProxy 方法中的 invoke 方法,最终达到的代理增强的效果。

5.执行方法

List<SysUser> sysUsers = sysUserMapper.selectAll();

综上所述,这里实际调用的其实是 MapperProxy.invoke(...) 方法,在该方法中一共进行了两个步骤:

    1. 将方法对象封装成一个MapperMethod对象 且存入Map缓存中,下次调用可以直接获取,不用再 new 。
    1. 执行 mapperMethod.execute(sqlSession, args); ,实际的执行方法

进入 execute 方法,首先看到根据 sql 的类型 进行了 switch ,这里我们聚焦 SELECE 分支中的 executeForMany(...) 方法 。

  1. 转换了参数(将参数封装成 array 或者 map)
  2. 判断是否分页 ( mybatis 的分页几乎不用,所以这里不关注)
  3. 执行 selectList 方法
  4. 封装响应结果

这里先看看 selectList 方法

  1. 进入 selectList 方法 获取 MappedStatement 对象 (记录了当前语句的所有信息)
  2. 进入 executor.query(...) 默认进入 BaseExecutor ,如果开启了二级缓存则是 CachingExecutor
    1. BaseExecutor 的query 方法 则 先获取了 BoundSql 对象,然后生成 CacheKey 再继续 query SelectList 最终执行的 方法:org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

最后会默认使用 cacheKey 从 一级缓存中获取数据,获取不到才从数据库中查询 ,并存储到缓存中。

  1. 设定ErrorContext ,这里有一个很重要的学习点,就是 mybatis 的异常处理方式,则是使用 ErrorContext (ThreadLocal)记录必要信息,在对应方法抛出异常时则可以获取 ErrorContext 中记录的信息,对异常进行描述
  2. 清理缓存,默认 queryStack 会是 0 ,所以进入query方法后 会先清理一次缓存
  3. 从一级缓存中获取结果
    • 能获取 直接处理返回
  4. 缓存中无法获取 - 查询数据库 并将结果添加到 一级缓存中
  • 最终返回

相关推荐
小鸡脚来咯13 小时前
springboot 整合mybatis
java·spring boot·mybatis
种树人2024081914 小时前
MyBatis xml 文件中 SQL 语句的小于号未转义导致报错
mybatis
码农派大星。16 小时前
MyBatis操作--进阶
mybatis
爱读源码的大都督17 小时前
MyBatis中的LanguageDriver的作用是什么
java·spring boot·mybatis
鹿屿二向箔17 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的快递管理系统
spring·mvc·mybatis
2的n次方_18 小时前
MyBatis——增删查改(XML 方式)
xml·数据库·mybatis
haozihua1 天前
4.Mybatis中,在Mapper的SQL映射文件中,使用<choose><when>无法识别参数的情况
java·sql·mybatis
Onlooker1292 天前
MyBatis5-缓存
缓存·mybatis
天幕繁星2 天前
JSqlParser、JavaCC实操
mybatis·mybatis plus·jsqlparser·javacc
漫天转悠2 天前
SQL注入攻击及其在SpringBoot中使用MyBatisPlus的防范策略
spring boot·mybatis