mybatis源码
mybatis执行体系
JDBC执行过程
执行过程分为四个模块
- 动态代理 MapperProxy
- SQL会话 SqlSession,降低调用复杂性
- 执行器 Executor
- JDBC处理器 StatementHandler
执行器
核心组件: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
执行流程
命中缓存条件
- 运行时参数
- 同一个会话(SqlSession)
- SQL语句、参数相同
- 相同的StatementID
- RowBounds相同
- 操作与配置相关
- 未手动清空缓存(提交、回滚)
- 未配置flushCache=true
- 未执行Update(增、删、改)
- 缓存作用域不是STATEMENT(如果是,嵌套查询也可以命中)
注:
- Sql和参数必须相同
- StatementID必须相同,SqlSession必须一样(会话级缓存),方法名、类、接口名不一样会导致StatementID不相同
- Rowbound默认值可以命中缓存
一级缓存失效
- Spring集成Mybatis一级缓存失效,原因是Spring会去创建新的SqlSession,把查询事务放入同一个事务即可使一级缓存生效。
二级缓存
二级缓存定义
二级缓存也成为应用级缓存,作用范围是整个应用,可以跨线程使用,所以二级缓存命中率更高,适合缓存一些修改少的数据。
拓展:
一级缓存不需要担心内存情况,因为SqlSession的生命周期较短。
二级缓存存储方式和结构
存储方式:
- 内存,容易导致OOM,需要限制容量
- 硬盘
- 第三方集成,如:redis
结构:key-value结构
数据溢出淘汰机制:
- FIFO队列,先进先出
- LRU最近最少使用
- 设置过期清理
线程安全、命中率统计、序列化
二级缓存配置表
| 二级缓存配置表 | |
| cacheEnabled | 全局缓存开关,默认true |
| useCache | statement缓存开关,默认true |
| flushCache | 清除默认:修改true、查询false |
| `<`cache/`>`或@CacheNamespace | 声明缓存空间 |
| `<`cache-red`/>`或@CacheNamespaceRef | 引用缓存空间 |
注意事项
- 一级缓存不需要提交即可命中缓存,因为都是在一个SqlSession中。
- 二级缓存必须要提交后才能够从缓存中获取,因为二级缓存拥有多个会话,存在线程安全问题。
二级缓存执行流程
二级缓存每个会话都有一个缓存管理器,缓存管理器中有多个暂存区,每个暂存区只能指定一个缓存区。在会话未提交之前所有的数据都放在暂存区中,会话提交后放入缓存区。
StatementHandler定义与结构
定义
JDBC处理器,基于JDBC构建Statement并设置参数,然后执行Sql。没调用会话当中一次SQL,都会有与之相对应的且唯一的Statement实例。
结构
执行流程分析
执行-->预编译-->设置参数-->执行-->结果集映射
参数处理
- 单个参数,默认不做任何处理,除非设置了@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]
设置属性之
- 可设置子属性值
- 支持自动创建子属性(必须带有空参构造方法,且不能是集合)
属性填充
循环依赖
填充属性-->获取嵌套查询值-->执行准备-->是否命中