2019.9.22笔记——mybatis源码解析之缓存实现原理
mybatis多参数报错误问题分析及解决方法
问题分析
在mybatis以前的版本在mapper类的方法中传递多参数时如果不用@Param传递参数名就会报错
如果从源码上分析我们可以观察到mybaits的底层是通过jdk反射获取参数名,然后通过参数名将
#{}和
${}中同名的参数替换成真正的数值的,当然
#{}和
${}的底层实现也是不一样的,前者是预编译,后者是单纯的在发送sql之前替换数值的。
之所以会出现这个问题是因为jdk8以前反射得到的方法参数名默认都是arg0、arg1、、、、、、
如果@Param注解又可以解决这个问题
其实在springMVC中也有需要拿到方法参数的地方,但是spirngMVC不会出现这个问题,因为spring直接是通过字节码操作的,没用使用jdk的反射获取。
解决方法
当然在jdk1.8之后就可以解决这个问题了,当然还需要在编译器上做一些处理,这里主要介绍了Idea和eclipse的解决方法。
- Idea
在j编译环境是dk8以后可以通过加上这个参数
-parameters,如果项目很大不会每次自动编译可以使用maven主动用complie去编译一次项目
- eclipse
在eclipse中可以通过勾选如下选项拿到参数名(同样需要jdk8以上)
mybatis缓存的实现及分析
mybatis的缓存的实现不管是一级缓存还是二级缓存底层都是通过一个map结构实现的
一级、二级缓存的特点
- 一级缓存
1、一级缓存不能关闭,默认开启。
2、默认作用域是单个sqlsession。
3、可以改变作用域,总共两个作用域,分别是单个sqlsession或者一个statement,如果是statement相当于没有缓存(因为发送一个sql就是一个statement)。
4、每个namespace的一级缓存是相互隔离的。
- 二级缓存
1、全局默认开启,但是需要手动加在需要缓存的mapper上,通过@CacheNamespace开启某个命名空间的二级缓存。
2、二级缓存的作用域是所有sqlsession。
3、每个namespace的二级缓存也是相互隔离的。
需要注意所有缓存都是有单位的,都是namespace(命名空间),都是不能跨命名空间的,不同namespace的缓存存储在不同map中,namespace就是mapper类对象的全名(含包名)。
缓存的查询顺序
先查询二级缓存,再查询一级缓存,最后查询数据库
一级缓存的的缓存对象
一级缓存的的缓存对象里主要有着一个map和缓存的id(namespace),被封装在了一个PerpetualCache对象中。
在查询前会先从一级缓存中检查缓存中是否有对应的数据
一级缓存在查询出来结果后将key和数据放到map中去
二级缓存对象
二级缓存的缓存对象是将一级缓存的对象层层封装,属于装饰者模式,下面都是二级缓存对象,由上到下代表封装的顺序,最上面是最外层的对象。
每一个缓存对象都有各自的作用,最里层的PerpetualCache对象就是一级缓存对象,作用也同一级缓存对象一致。
- SynchronizedCache:同步Cache,实现比较简单,直接使用synchronized修饰方法。
- LoggingCache:日志功能,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
- SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。
- LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value。
- PerpetualCache:作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。
这上面有一个个特殊的缓存对象LruCache,这个缓存对象默认是不开启的,只有在配置了缓存刷新间隔之后才能起作用,而且还可以配置它的回收规则
- LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值
- FIFO(先进先出): 按对象进入缓存的顺序来移除它们
- SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象
- WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象
必须配置flushInterval属性,而且缓存只有在执行增删改查的时候才会主动刷新缓存
下面就是二级缓存在无事务的状态下的基本调用
事务管理器
如果有事务存在,二级缓存会交给一个事务管理器对象去管理,这个对象里会有一个map集合,里面存放着子事务管理器。
这个子事务管理器中有一个map对象储存着在事务开启之后和事务提交之前的所有缓存,同时也存在一个真正的二级缓存对象,也就是交给事务管理器管理的缓存对象。还存在一个set集合,这个集合存放着未命中的缓存。
如果事务提交失败就不会将map中的数据更新到真正的二级缓存对象中,如果成功会将map中的数据更新到真正的二级缓存对象中去。
那个set集合是为了解决缓存穿透的,在事务提交的时候会将这个set中的缓存把真正缓存中同样的数据清空。这样也能解决二级缓存所带来的脏读幻读。
CacheKey的生成
我们知道每个缓存数据都会存在一个map中,那么自然会存在一个key来拿到缓存的数据。那么CacheKey是如何生成的呢?如何才能保证生成的key一定能拿到对应的数据?怎么才能知道两条sql语句是查询同一数据的?
这样看来CacheKey的生成规则就是关键,那么它是根据什么计算出来的呢?
CacheKey主要是生成其中的hashcode,生成的依据如下:
- sql的id(namespace加上mapper的id)
- 如果开启分页,起始
- sql语句
- 传递的参数
我们知道缓存放在一个hashmap中,所以key是否相等是通过
equals方法比较的,所以同时还重写了过了CacheKey的
equals方法。
public boolean equals(Object object) { if (this == object) { return true; } else if (!(object instanceof CacheKey)) { return false; } else { CacheKey cacheKey = (CacheKey)object; if (this.hashcode != cacheKey.hashcode) { return false; } else if (this.checksum != cacheKey.checksum) { return false; } else if (this.count != cacheKey.count) { return false; } else { for(int i = 0; i < this.updateList.size(); ++i) { Object thisObject = this.updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (thisObject == null) { if (thatObject != null) { return false; } } else if (!thisObject.equals(thatObject)) { return false; } } return true; } } }
- Mybatis3源码分析(17)-Sql解析执行-缓存的实现
- 通过源码分析MyBatis的缓存/Mybatis解析动态sql原理分析
- 史上最详尽 Java 8 集合类 HashMap : 底层实现和原理学习笔记(源码解析)
- 笔记-iOS弹幕(源码)实现原理解析
- 秋色园QBlog技术原理解析:博客一键安装工具技术实现[附源码下载]
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Mybatis实现原理深入解析
- Mybatis 代码流程及实现原理解析(一)
- Android 带你从源码的角度解析Scroller的滚动实现原理
- Redis 源码解析 string内部实现原理之链表
- 神经网络中 BP 算法的原理与 PYTHON 实现源码解析
- Android 带你从源码的角度解析Scroller的滚动实现原理
- 笔记:深入解析MapReduce架构设计与实现原理 第2章 MapReduce设计理念和基本架构
- Android 从源码的角度解析Scroller的滚动实现原理
- Android 图片三级缓存加载框架原理解析与代码实现
- Vue2.0源码阅读笔记--双向绑定实现原理
- 深入理解mybatis原理(五) MyBatis缓存机制的设计与实现
- 以 Okhttp3源码 为例 ------ 图解 缓存机制 的原理和实现(下)
- Spring AOP源码解析——AOP动态代理原理和实现方式
- Android 带你从源码的角度解析Scroller的滚动实现原理