Mybatis体系详解

205 阅读6分钟

mybatis源码

mybatis执行体系

JDBC执行过程

02.png

执行过程分为四个模块

  • 动态代理 MapperProxy
  • SQL会话 SqlSession,降低调用复杂性
  • 执行器 Executor
  • JDBC处理器 StatementHandler

执行器

01.png

核心组件:SqlSession

SqlSession,门面模式只提供API,调用executor,具体执行交给执行器Executor,executor基本功能只有改和查,所有的增删改归结成改和查,改和查会涉及缓存,查先从缓存查,改要删除缓存,executor维护缓存,它的辅助功能有提交、关闭执行器、批处理刷新功能。

Executor(执行器)

1. SimpleExecutor(简单执行器)

每次都会创建一个新的预处理器(PrepareStatement)

doQuery需要五个参数:

  • SQL声明映射
  • 参数
  • 行范围,是否需要分页
  • 结果处理器
  • 动态SQL语句

2. ReuseExecutor(可重用执行器)

会重用第一次执行创建的预处理器(PrepareStatement)

3. BatchExecutor(批处理执行器)

只针对修改操作,查询操作和简单执行器相同。需要进行手动刷新,调用执行器的doFlushStatements(false);对应批处理刷新

doUpdate需要两个参数

  • SQL声明(MappedStatement)
  • 具体SQL参数

4. BaseExecutor(基础执行器)

将上述的执行公共部分抽取出来,如一级缓存,获取链接等操作

实现Query或者Update对应doQuery和doUpdate,上述三个执行器都会实现doQuery和doUpdate。

5. CachingExecutor(缓存执行器)

主要目的实现二级缓存

缓存执行顺序,先走二级缓存,如果没有再去找一级缓存,如果还没有从数据库中查询

只专注于实现二级缓存的逻辑,对BaseExecutor进行装饰,除了二级缓存相关的实现,其他方法都会通过delegate调用下一个BaseExecutor的方法。

注:在不改变原有类结构和继承的情况下,通过包装原对象去产生一个新功能。

一级缓存

一级缓存默认打开的,存储格式是Key-Value,对应HashMap

执行流程

03.png

命中缓存条件

  • 运行时参数
    • 同一个会话(SqlSession)
    • SQL语句、参数相同
    • 相同的StatementID
    • RowBounds相同
  • 操作与配置相关
    • 未手动清空缓存(提交、回滚)
    • 未配置flushCache=true
    • 未执行Update(增、删、改)
    • 缓存作用域不是STATEMENT(如果是,嵌套查询也可以命中)

注:

  1. Sql和参数必须相同
  2. StatementID必须相同,SqlSession必须一样(会话级缓存),方法名、类、接口名不一样会导致StatementID不相同
  3. Rowbound默认值可以命中缓存

一级缓存失效

  • Spring集成Mybatis一级缓存失效,原因是Spring会去创建新的SqlSession,把查询事务放入同一个事务即可使一级缓存生效。

二级缓存

二级缓存定义

二级缓存也成为应用级缓存,作用范围是整个应用,可以跨线程使用,所以二级缓存命中率更高,适合缓存一些修改少的数据。

拓展:

一级缓存不需要担心内存情况,因为SqlSession的生命周期较短。

二级缓存存储方式和结构

存储方式:

  • 内存,容易导致OOM,需要限制容量
  • 硬盘
  • 第三方集成,如:redis

结构:key-value结构

数据溢出淘汰机制:

  • FIFO队列,先进先出
  • LRU最近最少使用
  • 设置过期清理

线程安全、命中率统计、序列化

04.png

二级缓存配置表

二级缓存配置表
cacheEnabled 全局缓存开关,默认true
useCache statement缓存开关,默认true
flushCache 清除默认:修改true、查询false
`<`cache/`>`或@CacheNamespace 声明缓存空间
`<`cache-red`/>`或@CacheNamespaceRef 引用缓存空间

注意事项

  • 一级缓存不需要提交即可命中缓存,因为都是在一个SqlSession中。
  • 二级缓存必须要提交后才能够从缓存中获取,因为二级缓存拥有多个会话,存在线程安全问题。

05.png

二级缓存执行流程

二级缓存每个会话都有一个缓存管理器,缓存管理器中有多个暂存区,每个暂存区只能指定一个缓存区。在会话未提交之前所有的数据都放在暂存区中,会话提交后放入缓存区。

06.png

StatementHandler定义与结构

定义

JDBC处理器,基于JDBC构建Statement并设置参数,然后执行Sql。没调用会话当中一次SQL,都会有与之相对应的且唯一的Statement实例。

结构

07.png

执行流程分析

执行-->预编译-->设置参数-->执行-->结果集映射

08.png

参数处理

  • 单个参数,默认不做任何处理,除非设置了@Param,会转换成Map
  • 多个参数,都会转换成Map
    • 情况一:会转换成Param1、Param2....
    • 情况二:基于Param中属性转换
    • 情况三:基于反射转换成变量名、如果不支持转换成arg0,arg1,关于反射获取变量名,只有jdk1.8并添加了-parameters参数才会起作用(不建议)
  • 映射参数处理
    • 直接映射,忽略SQL中引用的名称
    • Map类型,基于Map-Key映射
    • Object基于属性名称映射,支持潜逃对象属性访问

结果集处理

ResultSetHandler将结果集行转换成对象,然后通过ResultContext存放当前行的对象,以及解析状态和控制解析数量,最后将解析结果存入ResultHandler中的List中。

ResultMap

手动映射

Java对象和TableRow进行转换,ResultMap进行映射操作,ResultMap必填属性Type确定类型,

ResultMapping和ResultMap是一对多的,一个ResultMap对应多个ResultMapping,ResultMapping拥有五种表现形式,用于java中字段和数据库列进行映射,通过TypeHandler进行类型转换。

ResultMapping表现形式

  • 构造参数字段
  • ID字段
  • 普通字段
  • 关联字段
  • 集合关联字段

映射主要分为三种:

  • 复合映射
  • 嵌套查询
  • 外部映射

自动映射

满足条件

  • 列名和属性名同时存在(忽略大小写,驼峰)
  • 当前列未手动设置映射
  • 属性类别存在TypeHandler
  • 开启AutoMapping(默认开启)

Mybatis映射体系

MateObject

查询属性

  • 忽略大小写
  • 支持驼峰
  • 支持子属性

获取属性值

  • 基于**.**获取子属性,如:user.name
  • 基于索引获取列表值,如users[1].id
  • 基于Key获取Map值,user[name]

设置属性之

  • 可设置子属性值
  • 支持自动创建子属性(必须带有空参构造方法,且不能是集合)

09.png

属性填充

循环依赖

填充属性-->获取嵌套查询值-->执行准备-->是否命中