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;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值