MyBatis源码阅读
mybatis分为主要分为4个部分
- 处理JDBC链接
- 进行参数处理
- 会话处理
- 动态代理
我们先来看会话的部分-SqlSession
SqlSession采用门面模式,其中SqlSession只提供接口Api,实现功能全都交给Executor执行器执行。
Executor的基本功能只提供 修改,查询和缓存维护的操作。
其中修改和查询是JDBC原生只提供的接口
SimpleExecutor:简单执行器
其中只提供最基本的方法。
例如:
其中doQuery需要5个参数,分别为图片中的参数
当执行SimpleExecutor中同一个select语句多次,会发现预编译了多次,导致性能下降。
ReuseExecutor:重用执行器
在执行增删改操作(doUpdate)操作时,才会生效,假如进行查询操作(doQuery),则会默认调用SimpleExecutor。
在doUpdate操作后,必须进行批处理刷新(doFlushStatements)才会提交事务。
重用执行器,只要sql语句一样,无论缓存,都会只进行一次预编译。
BaseExecutor
讲三个执行器的方法都抽象出来,组成一个类,这个类就是BaseExecutor。
除了公共代码,类中还包含了doUpdate、doQuery的抽象接口,具体放在执行器中进行重写(模板方法设计模式)。
CachingExecutor:二级缓存
使用装饰者模式,本身实现了Executor。在处理完二级缓存后,本身的delegate属性会将其他的处理交给BaseExecutor。
但是二级缓存默认不提交,需要执行CachingExecutor.commit(true)才会进行提交。
一级缓存启用条件
- 参数相同
- 方法相同
- 同一个sqlSession
- 分页参数相同
怎样关闭一级缓存呢?
答案是无法关闭,只能将一级缓存的作用域缩小至statement,这时只有嵌套查询才可以使用缓存,其他无法使用缓存
那么Spring和MyBatis进行集成的情况下,有什么情况会导致一级缓存失效呢?
如果通过Bean获取mapper,Spring每次会新创建一个会话,导致缓存失效。
那么怎么解决呢:将需要获取缓存的代码放在一个事务里,就会存在一级缓存。
二级缓存
二级缓存可以跨线程使用,并且具有更高的命中率。所以适合放一些不经常修改的数据。
缓存的需求应该是一下几点
- 缓存可以放在硬盘、内存、或者第三方集成里
- 内存淘汰机制:FIFO、LIU(最近最少使用)
- 过期清理
- 线程安全(跨线程使用)
- 命中率统计
- 序列化
- …
我们可以看一下Cache的设计模式,很典型用了责任链模式
可以看到Cache接口只提供了一些基本的方法,其余的所有功能都用其他方法实现。
并且在各个方法之间,都使用了装饰器模式,一个接着一个进行传递
所有对缓存的更改,都会在commit后,才会生效。包括增加缓存和清空缓存。
为什么?
因为二级缓存存在多线程,如果两个线程一个查询,一个新增,会导致数据不一致。
Options
二级缓存中,需要设置namespace。其中在xml中,需要对cache的引用(两个空间默认的cache空间名字不相同)
一个会话对应着1个事务缓存管理器(TransactionalCacheManager),对应着多个暂存区,每个暂存区对应唯一的一个缓存区(二级缓存)。只有当commit的时候,暂存区的对象才放到缓存区里。(其实对应TransactionCaches只是一个Cache接口)