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 通过
mapperRegistry
的getMapper
方法,使用动态代理的方式构建了 Mapper 的实例。在调用 Mapper 的方法时,实际调用的事MapperProxy
方法中的invoke
方法,最终达到的代理增强的效果。
5.执行方法
List<SysUser> sysUsers = sysUserMapper.selectAll();
综上所述,这里实际调用的其实是 MapperProxy.invoke(...)
方法,在该方法中一共进行了两个步骤:
-
- 将方法对象封装成一个MapperMethod对象 且存入Map缓存中,下次调用可以直接获取,不用再 new 。
-
- 执行
mapperMethod.execute(sqlSession, args);
,实际的执行方法
- 执行
进入 execute 方法,首先看到根据 sql 的类型 进行了 switch ,这里我们聚焦 SELECE
分支中的 executeForMany(...)
方法 。
- 转换了参数(将参数封装成 array 或者 map)
- 判断是否分页 ( mybatis 的分页几乎不用,所以这里不关注)
- 执行 selectList 方法
- 封装响应结果
这里先看看 selectList
方法
- 进入 selectList 方法 获取
MappedStatement
对象 (记录了当前语句的所有信息) - 进入
executor.query(...)
默认进入BaseExecutor
,如果开启了二级缓存则是CachingExecutor
- 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)
- BaseExecutor 的query 方法 则 先获取了
最后会默认使用 cacheKey 从 一级缓存中获取数据,获取不到才从数据库中查询 ,并存储到缓存中。
- 设定ErrorContext ,这里有一个很重要的学习点,就是 mybatis 的异常处理方式,则是使用
ErrorContext (ThreadLocal)
记录必要信息,在对应方法抛出异常时则可以获取ErrorContext
中记录的信息,对异常进行描述 - 清理缓存,默认 queryStack 会是 0 ,所以进入query方法后 会先清理一次缓存
- 从一级缓存中获取结果
- 能获取 直接处理返回
- 缓存中无法获取 - 查询数据库 并将结果添加到 一级缓存中
- 最终返回