Mybatis架构分析和工作原理

Mybatis工作流程

Mybatis架构分析和工作原理_第1张图片
工作流程图

1、解析配置文件,包括全局配置文件和映射器配置文件,把它们解析成一个 Configuration 对象。
2、创建会 话 工 厂 SqlSessionFactory
3、操作数据库的接口,它在应用程序和数据库中间,代表我们跟数据库之间的一次连接:创建 SqlSession 对象。
4、SqlSession 持有了一个 Executor 对象,用来封装对数据库的操作。
5、在执行器 Executor 执行 query 或者 update 操作的时候我们创建一系列的对象,来处理参数、执行 SQL、处理结果集。

架构分层和模块划分

github代码

Mybatis架构分析和工作原理_第2张图片
代码架构.png

Mybatis架构分析和工作原理_第3张图片
架构图.png

接口层
接口层是最重要的一层,核心对象是 SqlSession,它是上层应用和 MyBatis打交道的桥梁,SqlSession 上定义了非常多的对数据库的操作方法。接口层在接收到调用请求的时候,会调用核心处理层的相应模块来完成具体的数据库操作。

核心处理层
所有跟数据库操作相关的动作都是在这一层完成的,插件也属于核心层,这是由它的工作方式和拦截的对象决定的。

主要做的事情:

  1. 把接口中传入的参数解析并且映射成 JDBC 类型;
  2. 解析 xml 文件中的 SQL 语句,包括插入参数,和动态 SQL 的生成;
  3. 执行 SQL 语句;
  4. 处理结果集,并映射成 Java 对象。

基础支持层
基础支持层主要是一些抽取出来的通用的功能(实现复用),用来支持核心处理层的功能。比如数据源、缓存、日志、xml 解析、反射、IO、事务等等这些功能。

Mybatis缓存

缓存是一般的 ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。跟 Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

缓存体系结构
MyBatis 跟缓存相关的类都在 cache 包里面,其中有一个 Cache 接口,只有一个默认的实现类 PerpetualCache,它是用 HashMap 实现的。

这里用到了装饰器模式,通过这些装饰器可以额外实现很多的功能:回收策略、日志记录、定时刷新等等。

通过查看代码,缓存实现类总体可分为三类:基本缓存、淘汰算法缓存、装饰器缓存

缓存实现类 描述 作用 装饰条件
基本缓存 缓存基本实现类 默认是 PerpetualCache,也可以自定义比如RedisCache、EhCache 等,具备基本功能的缓存类
LruCache LRU 策略的缓存 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) eviction="LRU"(默认)
FifoCache FIFO 策略的缓存 当缓存到达上限时候,删除最先入队的缓存 eviction="FIFO"
SoftCache、WeakCache 带清理策略的缓存 通过 JVM 的软引用和弱引用来实现缓存,当 JVM内存不足时,会自动清理掉这些缓存,基于SoftReference 和 WeakReference eviction="SOFT" eviction="WEAK"
LoggingCache 带日志功能的缓存 比如:输出缓存命中率 基本
SynchronizedCache 同步缓存 基于 synchronized 关键字实现,解决并发问题 基本
BlockingCache 阻塞缓存 通过在 get/put 方式中加锁,保证只有一个线程操作缓存,基于 Java 重入锁实现 blocking=true
SerializedCache 支持序列化的缓存 将对象序列化以后存到缓存中,取出时反序列化 readOnly=false(默认)
ScheduledCache 定时调度的缓存 在进行 get/put/remove/getSize 等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存--即每隔一段时间清空一次缓存 flushInterval 不为空
TransactionalCache 事务缓存 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 在TransactionalCacheManager 中用 Map维护对应关系

一级缓存

一级缓存也叫本地缓存,MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置。

Mybatis架构分析和工作原理_第4张图片
一级缓存.png

如果要在同一个会话里面共享一级缓存,这个对象肯定是在 SqlSession 里面创建的,作为 SqlSession 的一个属性。而DefaultSqlSession实现了SqlSession,对应的要实现他的所有方法。

DefaultSqlSession 里面只有两个属性,Configuration 是全局的,所以缓存只可能放在 Executor 里面维护,SimpleExecutor /ReuseExecutor/BatchExecutor 的父类BaseExecutor 的构造函数中持有了 PerpetualCache。

同一个会话里面,多次执行相同的 SQL 语句,会直接从内存取到缓存的结果,不会再发送 SQL 到数据库。但是不同的会话里面,即使执行的 SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

一级缓存的缺点
使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存

二级缓存

二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。二级缓存是默认关闭的

一级缓存和二级缓存同时存在的时候,那个先执行?

二级缓存作为一个作用范围更广的缓存,他是在SqlSession的外层,否则不可能被多个SqlSession共享,而一级缓存是在SqlSession的内部的,所以在工作在一级缓存之前,也就是说只有取不到二级情况下才到会话中去取一级缓存

二级缓存在哪里维护的?

由于二级缓存是跨会话共享的,SqlSession本身和它里面的BaseExecutor已经满足不了需求了,所以需要用到CachingExecutor这个类,启用了二级缓存的话,CachingExecutor对于查询请求,会判断二级缓存中是否有缓存结果,如果有就直接返回,如果没有则委派交给真正的查询器Executor实现类

Mybatis架构分析和工作原理_第5张图片
二级缓存.png
开启二级缓存

1、在 mybatis-config.xml 中配置了(可以不配置,默认是 true),只要没有显式地设置 cacheEnabled=false,都会用 CachingExecutor 装饰基本的执行器。

2、在 Mapper.xml 中配置标签



    eviction ="LRU" 
    flushInterval ="120000" 
    readOnly =" false" "/> 

cache属性介绍:

属性 含义 取值
type 缓存实现类 需要实现 Cache 接口,默认是 PerpetualCache
size 最多缓存对象个数 默认1024
eviction 回收策略(缓存淘汰算法) LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval 定时自动清空缓存间隔 自动刷新时间,单位 ms,未配置时只有调用时刷新
readOnly 是否只读 true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安全,因此默认是 false。改为 false 可读写时,对象必须支持序列化。
blocking 是否使用可重入锁实现缓存的并发控制 true,会使用 BlockingCache 对 Cache 进行装饰默认 false

Mapper.xml 配置了之后,select()会被缓存。update()、delete()、insert()会刷新缓存。

在单个 Statement ID 上显式关闭二级缓存(默认是 true)