|
|
|
@ -0,0 +1,617 @@
|
|
|
|
|
MyBatis中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是Cache接口的实现。MyBatis缓存模块的设计 使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
|
|
|
|
|
## 1 Cache组件
|
|
|
|
|
MyBatis中缓存模块相关的代码位于org.apache.ibatis.cache包下,其中Cache接口是缓存模块中最核心的接口,它定义了所有缓存的基本行为。
|
|
|
|
|
```java
|
|
|
|
|
public interface Cache {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取当前缓存的Id
|
|
|
|
|
*/
|
|
|
|
|
String getId();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 存入缓存的key和value,key一般为CacheKey对象
|
|
|
|
|
*/
|
|
|
|
|
void putObject(Object key, Object value);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据key获取缓存值
|
|
|
|
|
*/
|
|
|
|
|
Object getObject(Object key);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除指定的缓存项
|
|
|
|
|
*/
|
|
|
|
|
Object removeObject(Object key);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 清空缓存
|
|
|
|
|
*/
|
|
|
|
|
void clear();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取缓存的大小
|
|
|
|
|
*/
|
|
|
|
|
int getSize();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
* 获取读写锁,可以看到,这个接口方法提供了默认的实现!!
|
|
|
|
|
* 这是Java8的新特性!!只是平时开发时很少用到!!!
|
|
|
|
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
*/
|
|
|
|
|
default ReadWriteLock getReadWriteLock() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
如下图所示,Cache接口的实现类有很多,但大部分都是装饰器,只有PerpetualCache提供了Cache 接口的基本实现。
|
|
|
|
|
|
|
|
|
|
![avator](/images/mybatis/Cache组件.png)
|
|
|
|
|
### 1.1 PerpetualCache
|
|
|
|
|
PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用HashMap记录缓存项,也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。
|
|
|
|
|
```java
|
|
|
|
|
public class PerpetualCache implements Cache {
|
|
|
|
|
|
|
|
|
|
// Cache对象的唯一标识
|
|
|
|
|
private final String id;
|
|
|
|
|
|
|
|
|
|
// 其所有的缓存功能实现,都是基于JDK的HashMap提供的方法
|
|
|
|
|
private Map<Object, Object> cache = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
public PerpetualCache(String id) {
|
|
|
|
|
this.id = id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getId() {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getSize() {
|
|
|
|
|
return cache.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void putObject(Object key, Object value) {
|
|
|
|
|
cache.put(key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object getObject(Object key) {
|
|
|
|
|
return cache.get(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object removeObject(Object key) {
|
|
|
|
|
return cache.remove(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void clear() {
|
|
|
|
|
cache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 其重写了Object中的equals()和hashCode()方法,两者都只关心id字段
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
|
if (getId() == null) {
|
|
|
|
|
throw new CacheException("Cache instances require an ID.");
|
|
|
|
|
}
|
|
|
|
|
if (this == o) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!(o instanceof Cache)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cache otherCache = (Cache) o;
|
|
|
|
|
return getId().equals(otherCache.getId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int hashCode() {
|
|
|
|
|
if (getId() == null) {
|
|
|
|
|
throw new CacheException("Cache instances require an ID.");
|
|
|
|
|
}
|
|
|
|
|
return getId().hashCode();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
下面来看一下cache.decorators包下提供的装饰器,它们都直接实现了Cache接口,扮演着装饰器的角色。这些装饰器会在PerpetualCache的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。
|
|
|
|
|
### 1.2 BlockingCache
|
|
|
|
|
BlockingCache是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定key对应的数据。
|
|
|
|
|
```java
|
|
|
|
|
public class BlockingCache implements Cache {
|
|
|
|
|
|
|
|
|
|
// 阻塞超时时长
|
|
|
|
|
private long timeout;
|
|
|
|
|
// 持有的被装饰者
|
|
|
|
|
private final Cache delegate;
|
|
|
|
|
// 每个key都有其对应的ReentrantLock锁对象
|
|
|
|
|
private final ConcurrentHashMap<Object, ReentrantLock> locks;
|
|
|
|
|
|
|
|
|
|
// 初始化 持有的持有的被装饰者 和 锁集合
|
|
|
|
|
public BlockingCache(Cache delegate) {
|
|
|
|
|
this.delegate = delegate;
|
|
|
|
|
this.locks = new ConcurrentHashMap<>();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
假设线程A在BlockingCache中未查找到keyA对应的缓存项时,线程A会获取keyA对应的锁,这样后续线程A在查找keyA时,其它线程会被阻塞。
|
|
|
|
|
```java
|
|
|
|
|
// 根据key获取锁对象,然后上锁
|
|
|
|
|
private void acquireLock(Object key) {
|
|
|
|
|
// 获取key对应的锁对象
|
|
|
|
|
Lock lock = getLockForKey(key);
|
|
|
|
|
// 获取锁,带超时时长
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
|
try {
|
|
|
|
|
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
|
|
|
|
|
if (!acquired) { // 超时,则抛出异常
|
|
|
|
|
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
|
|
|
|
|
}
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
// 如果获取锁失败,则阻塞一段时间
|
|
|
|
|
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 上锁
|
|
|
|
|
lock.lock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ReentrantLock getLockForKey(Object key) {
|
|
|
|
|
// Java8新特性,Map系列类中新增的方法
|
|
|
|
|
// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
|
|
|
|
|
// 表示,若key对应的value为空,则将第二个参数的返回值存入该Map集合并返回
|
|
|
|
|
return locks.computeIfAbsent(key, k -> new ReentrantLock());
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
假设线程A从数据库中查找到keyA对应的结果对象后,将结果对象放入到BlockingCache中,此时线程A会释放keyA对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从BlockingCache中获取keyA对应的数据,而不是再次访问数据库。
|
|
|
|
|
```java
|
|
|
|
|
@Override
|
|
|
|
|
public void putObject(Object key, Object value) {
|
|
|
|
|
try {
|
|
|
|
|
// 存入key和其对应的缓存项
|
|
|
|
|
delegate.putObject(key, value);
|
|
|
|
|
} finally {
|
|
|
|
|
// 最后释放锁
|
|
|
|
|
releaseLock(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void releaseLock(Object key) {
|
|
|
|
|
ReentrantLock lock = locks.get(key);
|
|
|
|
|
// 锁是否被当前线程持有
|
|
|
|
|
if (lock.isHeldByCurrentThread()) {
|
|
|
|
|
// 是,则释放锁
|
|
|
|
|
lock.unlock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
### 1.3 FifoCache和LruCache
|
|
|
|
|
在很多场景中,为了控制缓存的大小,系统需要按照一定的规则清理缓存。FifoCache是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。
|
|
|
|
|
```java
|
|
|
|
|
public class FifoCache implements Cache {
|
|
|
|
|
|
|
|
|
|
// 被装饰对象
|
|
|
|
|
private final Cache delegate;
|
|
|
|
|
// 用一个FIFO的队列记录key的顺序,其具体实现为LinkedList
|
|
|
|
|
private final Deque<Object> keyList;
|
|
|
|
|
// 决定了缓存的容量上限
|
|
|
|
|
private int size;
|
|
|
|
|
|
|
|
|
|
// 国际惯例,通过构造方法初始化自己的属性,缓存容量上限默认为1024个
|
|
|
|
|
public FifoCache(Cache delegate) {
|
|
|
|
|
this.delegate = delegate;
|
|
|
|
|
this.keyList = new LinkedList<>();
|
|
|
|
|
this.size = 1024;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getId() {
|
|
|
|
|
return delegate.getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getSize() {
|
|
|
|
|
return delegate.getSize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setSize(int size) {
|
|
|
|
|
this.size = size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void putObject(Object key, Object value) {
|
|
|
|
|
// 存储缓存项之前,先在keyList中注册
|
|
|
|
|
cycleKeyList(key);
|
|
|
|
|
// 存储缓存项
|
|
|
|
|
delegate.putObject(key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void cycleKeyList(Object key) {
|
|
|
|
|
// 在keyList队列中注册要添加的key
|
|
|
|
|
keyList.addLast(key);
|
|
|
|
|
// 如果注册这个key会超出容积上限,则把最老的一个缓存项清除掉
|
|
|
|
|
if (keyList.size() > size) {
|
|
|
|
|
Object oldestKey = keyList.removeFirst();
|
|
|
|
|
delegate.removeObject(oldestKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object getObject(Object key) {
|
|
|
|
|
return delegate.getObject(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object removeObject(Object key) {
|
|
|
|
|
return delegate.removeObject(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 除了清理缓存项,还要清理key的注册列表
|
|
|
|
|
@Override
|
|
|
|
|
public void clear() {
|
|
|
|
|
delegate.clear();
|
|
|
|
|
keyList.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
LruCache是按照"近期最少使用算法"(Least Recently Used, LRU)进行缓存清理的装饰器,在需要清理缓存时,它会清除最近最少使用的缓存项。
|
|
|
|
|
```java
|
|
|
|
|
public class LruCache implements Cache {
|
|
|
|
|
|
|
|
|
|
// 被装饰者
|
|
|
|
|
private final Cache delegate;
|
|
|
|
|
// 这里使用的是LinkedHashMap,它继承了HashMap,但它的元素是有序的
|
|
|
|
|
private Map<Object, Object> keyMap;
|
|
|
|
|
// 最近最少被使用的缓存项的key
|
|
|
|
|
private Object eldestKey;
|
|
|
|
|
|
|
|
|
|
// 国际惯例,构造方法中进行属性初始化
|
|
|
|
|
public LruCache(Cache delegate) {
|
|
|
|
|
this.delegate = delegate;
|
|
|
|
|
// 这里初始化了keyMap,并定义了eldestKey的取值规则
|
|
|
|
|
setSize(1024);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setSize(final int size) {
|
|
|
|
|
// 初始化keyMap,同时指定该Map的初始容积及加载因子,第三个参数true表示该LinkedHashMap
|
|
|
|
|
// 记录的顺序是accessOrder,即,LinkedHashMap.get()方法会改变其中元素的顺序
|
|
|
|
|
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
|
|
|
|
|
private static final long serialVersionUID = 4267176411845948333L;
|
|
|
|
|
|
|
|
|
|
// 当调用LinkedHashMap.put()方法时,该方法会被调用
|
|
|
|
|
@Override
|
|
|
|
|
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
|
|
|
|
|
boolean tooBig = size() > size;
|
|
|
|
|
if (tooBig) {
|
|
|
|
|
// 当已达到缓存上限,更新eldestKey字段,后面将其删除
|
|
|
|
|
eldestKey = eldest.getKey();
|
|
|
|
|
}
|
|
|
|
|
return tooBig;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 存储缓存项
|
|
|
|
|
@Override
|
|
|
|
|
public void putObject(Object key, Object value) {
|
|
|
|
|
delegate.putObject(key, value);
|
|
|
|
|
// 记录缓存项的key,超出容量则清除最久未使用的缓存项
|
|
|
|
|
cycleKeyList(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void cycleKeyList(Object key) {
|
|
|
|
|
keyMap.put(key, key);
|
|
|
|
|
// eldestKey不为空,则表示已经达到缓存上限
|
|
|
|
|
if (eldestKey != null) {
|
|
|
|
|
// 清除最久未使用的缓存
|
|
|
|
|
delegate.removeObject(eldestKey);
|
|
|
|
|
// 制空
|
|
|
|
|
eldestKey = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object getObject(Object key) {
|
|
|
|
|
// 访问key元素 会改变该元素在LinkedHashMap中的顺序
|
|
|
|
|
keyMap.get(key); //touch
|
|
|
|
|
return delegate.getObject(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getId() {
|
|
|
|
|
return delegate.getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getSize() {
|
|
|
|
|
return delegate.getSize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object removeObject(Object key) {
|
|
|
|
|
return delegate.removeObject(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void clear() {
|
|
|
|
|
delegate.clear();
|
|
|
|
|
keyMap.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
### 1.4 SoftCache和WeakCache
|
|
|
|
|
在分析SoftCache和WeakCache实现之前,我们再温习一下Java提供的4种引用类型,强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
|
|
|
|
|
|
|
|
|
|
- 强引用
|
|
|
|
|
平时用的最多的,如Object obj = new Object(),新建的Object对象就是被强引用的。如果一个对象被强引用,即使是JVM内存空间不足,要抛出OutOfMemoryError异常,GC也绝不会回收该对象。
|
|
|
|
|
- 软引用
|
|
|
|
|
仅次于强引用的一种引用,它使用类SoftReference来表示。当JVM内存不足时,GC会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的SoftCache就是通过软引用实现的。
|
|
|
|
|
另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被GC回收掉了,所以通过 Reference.get()方法来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null,来判断被软引用的对象是否还存活。
|
|
|
|
|
- 弱引用
|
|
|
|
|
弱引用使用WeakReference表示,它不会阻止所引用的对象被GC回收。在JVM进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。
|
|
|
|
|
所以,只被弱引用所指向的对象,其生存周期是两次GC之间的这段时间,而只被软引用所指向的对象可以经历多次GC,直到出现内存紧张的情况才被回收。
|
|
|
|
|
- 虚引用
|
|
|
|
|
最弱的一种引用类型,由类PhantomReference表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。
|
|
|
|
|
- 引用队列(ReferenceQueue )
|
|
|
|
|
很多场景下,我们的程序需要在一个对象被GC时得到通知,引用队列就是用于收集这些信息的队列。在创建SoftReference对象时,可以为其关联一个引用队列,当SoftReference所引用的对象被GC时, JVM就会将该SoftReference对象添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些SoftReference对象。不仅是SoftReference,弱引用和虚引用都可以关联相应的队列。
|
|
|
|
|
|
|
|
|
|
现在来看一下SoftCache的具体实现。
|
|
|
|
|
```java
|
|
|
|
|
public class SoftCache implements Cache {
|
|
|
|
|
|
|
|
|
|
// 这里使用了LinkedList作为容器,在SoftCache中,最近使用的一部分缓存项不会被GC
|
|
|
|
|
// 这是通过将其value添加到hardLinksToAvoidGarbageCollection集合实现的(即,有强引用指向其value)
|
|
|
|
|
private final Deque<Object> hardLinksToAvoidGarbageCollection;
|
|
|
|
|
// 引用队列,用于记录已经被GC的缓存项所对应的SoftEntry对象
|
|
|
|
|
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
|
|
|
|
|
// 持有的被装饰者
|
|
|
|
|
private final Cache delegate;
|
|
|
|
|
// 强连接的个数,默认为256
|
|
|
|
|
private int numberOfHardLinks;
|
|
|
|
|
|
|
|
|
|
// 构造方法进行属性的初始化
|
|
|
|
|
public SoftCache(Cache delegate) {
|
|
|
|
|
this.delegate = delegate;
|
|
|
|
|
this.numberOfHardLinks = 256;
|
|
|
|
|
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
|
|
|
|
|
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class SoftEntry extends SoftReference<Object> {
|
|
|
|
|
private final Object key;
|
|
|
|
|
|
|
|
|
|
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
|
|
|
|
|
// 指向value的引用是软引用,并且关联了 引用队列
|
|
|
|
|
super(value, garbageCollectionQueue);
|
|
|
|
|
// 强引用
|
|
|
|
|
this.key = key;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void putObject(Object key, Object value) {
|
|
|
|
|
// 清除已经被GC的缓存项
|
|
|
|
|
removeGarbageCollectedItems();
|
|
|
|
|
// 添加缓存
|
|
|
|
|
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void removeGarbageCollectedItems() {
|
|
|
|
|
SoftEntry sv;
|
|
|
|
|
// 遍历queueOfGarbageCollectedEntries集合,清除已经被GC的缓存项value
|
|
|
|
|
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
|
|
|
|
|
delegate.removeObject(sv.key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object getObject(Object key) {
|
|
|
|
|
Object result = null;
|
|
|
|
|
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
|
|
|
|
|
// 用一个软引用指向 key对应的缓存项
|
|
|
|
|
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
|
|
|
|
|
// 检测缓存中是否有对应的缓存项
|
|
|
|
|
if (softReference != null) {
|
|
|
|
|
// 获取softReference引用的value
|
|
|
|
|
result = softReference.get();
|
|
|
|
|
// 如果softReference引用的对象已经被GC,则从缓存中清除对应的缓存项
|
|
|
|
|
if (result == null) {
|
|
|
|
|
delegate.removeObject(key);
|
|
|
|
|
} else {
|
|
|
|
|
synchronized (hardLinksToAvoidGarbageCollection) {
|
|
|
|
|
// 将缓存项的value添加到hardLinksToAvoidGarbageCollection集合中保存
|
|
|
|
|
hardLinksToAvoidGarbageCollection.addFirst(result);
|
|
|
|
|
// 如果hardLinksToAvoidGarbageCollection的容积已经超过numberOfHardLinks
|
|
|
|
|
// 则将最老的缓存项从hardLinksToAvoidGarbageCollection中清除,FIFO
|
|
|
|
|
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
|
|
|
|
|
hardLinksToAvoidGarbageCollection.removeLast();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Object removeObject(Object key) {
|
|
|
|
|
// 清除指定的缓存项之前,也会先清理被GC的缓存项
|
|
|
|
|
removeGarbageCollectedItems();
|
|
|
|
|
return delegate.removeObject(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void clear() {
|
|
|
|
|
synchronized (hardLinksToAvoidGarbageCollection) {
|
|
|
|
|
// 清理强引用集合
|
|
|
|
|
hardLinksToAvoidGarbageCollection.clear();
|
|
|
|
|
}
|
|
|
|
|
// 清理被GC的缓存项
|
|
|
|
|
removeGarbageCollectedItems();
|
|
|
|
|
// 清理最底层的缓存项
|
|
|
|
|
delegate.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getId() {
|
|
|
|
|
return delegate.getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int getSize() {
|
|
|
|
|
removeGarbageCollectedItems();
|
|
|
|
|
return delegate.getSize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setSize(int size) {
|
|
|
|
|
this.numberOfHardLinks = size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
WeakCache的实现与SoftCache基本类似,唯一的区别在于其中使用WeakEntry(继承了WeakReference)封装真正的 value 对象,其他实现完全一样。
|
|
|
|
|
|
|
|
|
|
另外,还有ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache等。ScheduledCache是周期性清理缓存的装饰器,它的clearInterval字段记录了两次缓存清理之间的时间间隔,默认是一小时,lastClear字段记录了最近一次清理的时间戳。ScheduledCache 的getObject()、putObject()、removeObject()等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
|
|
|
|
|
|
|
|
|
|
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段和 request 字段记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
|
|
|
|
|
|
|
|
|
|
SynchronizedCache通过在每个方法上添加 synchronized关键字,为Cache添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。
|
|
|
|
|
|
|
|
|
|
SerializedCache 提供了将 value 对象序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,井将序列化后的 byte[] 数组作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[] 数组反序列化成 Java 对象。不使用 SerializedCache 装饰器进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而 使用SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化。
|
|
|
|
|
## 2 CacheKey
|
|
|
|
|
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key进行比较,MyBatis 中因为涉及动态 SQL 等 多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类来表示缓存项的 key,在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同。
|
|
|
|
|
```java
|
|
|
|
|
public class CacheKey implements Cloneable, Serializable {
|
|
|
|
|
|
|
|
|
|
private static final long serialVersionUID = 1146682552656046210L;
|
|
|
|
|
|
|
|
|
|
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
|
|
|
|
|
|
|
|
|
|
private static final int DEFAULT_MULTIPLYER = 37;
|
|
|
|
|
private static final int DEFAULT_HASHCODE = 17;
|
|
|
|
|
|
|
|
|
|
// 参与计算hashcode,默认值DEFAULT_MULTIPLYER = 37
|
|
|
|
|
private final int multiplier;
|
|
|
|
|
// 当前CacheKey对象的hashcode,默认值DEFAULT_HASHCODE = 17
|
|
|
|
|
private int hashcode;
|
|
|
|
|
// 校验和
|
|
|
|
|
private long checksum;
|
|
|
|
|
private int count;
|
|
|
|
|
|
|
|
|
|
// 由该集合中的所有元素 共同决定两个CacheKey对象是否相同,一般会使用一下四个元素
|
|
|
|
|
// MappedStatement的id、查询结果集的范围参数(RowBounds的offset和limit)
|
|
|
|
|
// SQL语句(其中可能包含占位符"?")、SQL语句中占位符的实际参数
|
|
|
|
|
private List<Object> updateList;
|
|
|
|
|
|
|
|
|
|
// 构造方法初始化属性
|
|
|
|
|
public CacheKey() {
|
|
|
|
|
this.hashcode = DEFAULT_HASHCODE;
|
|
|
|
|
this.multiplier = DEFAULT_MULTIPLYER;
|
|
|
|
|
this.count = 0;
|
|
|
|
|
this.updateList = new ArrayList<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public CacheKey(Object[] objects) {
|
|
|
|
|
this();
|
|
|
|
|
updateAll(objects);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void update(Object object) {
|
|
|
|
|
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
|
|
|
|
|
// 重新计算count、checksum和hashcode的值
|
|
|
|
|
count++;
|
|
|
|
|
checksum += baseHashCode;
|
|
|
|
|
baseHashCode *= count;
|
|
|
|
|
hashcode = multiplier * hashcode + baseHashCode;
|
|
|
|
|
// 将object添加到updateList集合
|
|
|
|
|
updateList.add(object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getUpdateCount() {
|
|
|
|
|
return updateList.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void updateAll(Object[] objects) {
|
|
|
|
|
for (Object o : objects) {
|
|
|
|
|
update(o);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* CacheKey重写了equals()和hashCode()方法,这两个方法使用上面介绍
|
|
|
|
|
* 的count、checksum、hashcode、updateList比较两个CacheKey对象是否相同
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public boolean equals(Object object) {
|
|
|
|
|
// 如果为同一对象,直接返回true
|
|
|
|
|
if (this == object) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// 如果object都不是CacheKey类型,直接返回false
|
|
|
|
|
if (!(object instanceof CacheKey)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 类型转换一下
|
|
|
|
|
final CacheKey cacheKey = (CacheKey) object;
|
|
|
|
|
|
|
|
|
|
// 依次比较hashcode、checksum、count,如果不等,直接返回false
|
|
|
|
|
if (hashcode != cacheKey.hashcode) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (checksum != cacheKey.checksum) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (count != cacheKey.count) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 比较updateList中的元素是否相同,不同直接返回false
|
|
|
|
|
for (int i = 0; i < updateList.size(); i++) {
|
|
|
|
|
Object thisObject = updateList.get(i);
|
|
|
|
|
Object thatObject = cacheKey.updateList.get(i);
|
|
|
|
|
if (!ArrayUtil.equals(thisObject, thatObject)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public int hashCode() {
|
|
|
|
|
return hashcode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String toString() {
|
|
|
|
|
StringJoiner returnValue = new StringJoiner(":");
|
|
|
|
|
returnValue.add(String.valueOf(hashcode));
|
|
|
|
|
returnValue.add(String.valueOf(checksum));
|
|
|
|
|
updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
|
|
|
|
|
return returnValue.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public CacheKey clone() throws CloneNotSupportedException {
|
|
|
|
|
CacheKey clonedCacheKey = (CacheKey) super.clone();
|
|
|
|
|
clonedCacheKey.updateList = new ArrayList<>(updateList);
|
|
|
|
|
return clonedCacheKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 3 小结
|
|
|
|
|
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java 反射机制的封装;然后分析了类型转换 TypeHandler 组件,了解了 MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换。
|
|
|
|
|
|
|
|
|
|
之后分析了MyBatis 提供的 DataSource 模块的实现和原理,深入解析了 MyBatis 自带的连接池PooledDataSource 的详细实现;后面紧接着介绍了 Transaction 模块的功能。然后分析了 binding 模块如何将 Mapper 接口与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache 接口以及多个实现类的具体实现,它们是Mybatis中一级缓存和二级缓存的基础。
|