MyBatis源码分析(3)—— Cache接口以及实现
@(MyBatis)[Cache]
MyBatis源码分析——Cache接口以及实现
Cache接口
MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口。
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
Cache实现
Cache的实现类中,Cache有不同的功能,每个功能独立,互不影响,则对于不同的Cache功能,这里使用了装饰者模式实现。
PerpetualCache
作为为最基础的缓存类,底层实现比较简单,直接使用了HashMap。
FifoCache
FIFO回收策略,装饰类,内部维护了一个队列,来保证FIFO,一旦超出指定的大小,则从队列中获取Key并从被包装的Cache中移除该键值对。
public class FifoCache implements Cache {
// 被包装的类
private final Cache delegate;
// 队列,用来维持FIFO
private LinkedList<Object> keyList;
// 最大可容纳的大小
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<Object>();
this.size = 1024;
}
@Override
public void putObject(Object key, Object value) {
// 将Key放入队列中,并且检查一遍,如果满了则移除队列头部的元素
cycleKeyList(key);
// 执行真正的操作
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
// 将Key放入队列
keyList.addLast(key);
if (keyList.size() > size) {
// 超出指定容量,移除队列头部Key
Object oldestKey = keyList.removeFirst();
// 从缓存中移除Key对应的值
delegate.removeObject(oldestKey);
}
}
// 省略部分代码
...
}
LoggingCache
日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志。
public class LoggingCache implements Cache {
private Log log;
private Cache delegate;
// 请求次数
protected int requests = 0;
// 命中次数
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
// 命中
hits++;
}
if (log.isDebugEnabled()) {
// 如果开启了DEBUG模式,则输出命中率
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
// 获取命中率
private double getHitRatio() {
return (double) hits / (double) requests;
}
// 省略部分代码
...
}
LruCache
LRU回收策略,装饰类,在内部保存一个LinkedHashMap,用以实现LRU。
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
// 初始化设置LRU回收的边界容量
setSize(1024);
}
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 键值移除策略,当大于指定容量时则移除最近最少使用的key/value
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
// 保存需要移除的键,因为在被包装的类中并不知道什么键需要移除
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 将当前Key放到LRU的Map中,如果大于指定容量,则移除筛选的键值对
cycleKeyList(key);
}
@Override
public Object getObject(Object key) {
// 让当前LRU的Map知道使用过
keyMap.get(key); //touch
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
// 这里没有移除当前维护的key,不过在后续也会被回收,可以忽略
return delegate.removeObject(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
// 从Cache中移除掉LRU筛选出的键值对
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
// 省略部分代码...
}
ScheduledCache
定时清空Cache,但是并没有开始一个定时任务,而是在使用Cache的时候,才去检查时间是否到了。
public class ScheduledCache implements Cache {
private Cache delegate;
// 清除的时间间隔
protected long clearInterval;
// 上一次清除的时间
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = 60 * 60 * 1000; // 1 hour
this.lastClear = System.currentTimeMillis();
}
public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
}
@Override
public Object getObject(Object key) {
if (clearWhenStale()) {
return null;
} else {
return delegate.getObject(key);
}
}
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
// 时间到了,清空
clear();
return true;
}
return false;
}
@Override
public void clear() {
// 更新清空时间
lastClear = System.currentTimeMillis();
delegate.clear();
}
// 省略部分代码
}
SynchronizedCache
同步Cache,实现比较简单,直接使用synchronized修饰方法。
public class SynchronizedCache implements Cache {
private Cache delegate;
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
// 省略部分代码
}
SoftCache
软引用回收策略,软引用只有当内存不足时才会被垃圾收集器回收。这里的实现机制中,使用了一个链表来保证一定数量的值即使内存不足也不会被回收,但是没有保存在该链表的值则有可能会被回收。
在WeakHashMap中,可以看到是将引用应用到Key的,当Key被回收后,则移除相关的Value。但是这里是将其应用到Value中,因为Key不能被回收,如果被移除的话,就会影响到整个体系,最底层的实现使用HashMap实现的,没有Key,就没有办法移除相关的值。反过来,值被回收了,将软引用对象放到队列中,可以根据Key调用removeObject移除该关联的键和软引用对象。
public class SoftCache implements Cache {
// 用于保存一定数量强引用的值
private final LinkedList<Object> hardLinksToAvoidGarbageCollection;
// 引用队列,当被垃圾收集器回收时,会将软引用对象放入此队列
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
// 保存强引用值的数量
private int numberOfHardLinks;
public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
}
@Override
public void putObject(Object key, Object value) {
// 移除被垃圾收集器回收的键值
removeGarbageCollectedItems();
// 将软件用作用到Value中
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked")
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
// 该值被垃圾收集器回收,移除掉该项
delegate.removeObject(key);
} else {
// 这里以及下面的clear,想不通为什么要加hardLinksToAvoidGarbageCollection的同步?(在WeakCache中却没有加同步)
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
// 超出容量,则移除最先保存的引用
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
// 移除被垃圾收集器回收的键值
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
// 这里需要清空该队列,否则即使下面调用clear,其Map清空了,但是部分值保留有引用,垃圾收集器也不会回收,会造成短暂的内存泄漏。
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 清空被垃圾收集器回收的value其相关联的键以及软引用
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
// 这里软引用对象是用在value中的
private static class SoftEntry extends SoftReference<Object> {
// 保存与value相关联的Key,因为一旦被垃圾收集器回收,则此软引用对象会被放到关联的引用队列中,这样就可以根据Key,移除该键值对。
private final Object key;
private SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
// 省略部分代码
}
WeakCache
弱引用回收策略,弱引用的对象一旦被垃圾收集器发现,则会被回收,无论内存是否足够。这里的实现和上面的软引用类似,除了使用WeakReference替换掉SoftReference,其它基本一样。还有一点想不通的就是,为什么SoftCache加锁了,而这里没有加锁。
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
if (weakReference != null) {
result = weakReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
// 软引用这里加锁了
hardLinksToAvoidGarbageCollection.addFirst(result);
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
return result;
}
TransactionalCache
事务缓存,在提交的时候,才真正的放到Cache中,或者回滚的时候清除掉,对Cache没有影响。
public class TransactionalCache implements Cache {
private Cache delegate;
private boolean clearOnCommit;
private Map<Object, AddEntry> entriesToAddOnCommit;
private Map<Object, RemoveEntry> entriesToRemoveOnCommit;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, AddEntry>();
this.entriesToRemoveOnCommit = new HashMap<Object, RemoveEntry>();
}
@Override
public Object getObject(Object key) {
if (clearOnCommit) return null; // issue #146
return delegate.getObject(key);
}
@Override
public void putObject(Object key, Object object) {
// 移除当前事务中 待移除键值对操作
entriesToRemoveOnCommit.remove(key);
// 添加当前事务中 待增加到缓存键值对操作
entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
}
@Override
public Object removeObject(Object key) {
// 移除增加该键值对的操作
entriesToAddOnCommit.remove(key);
// 添加移除键值对操作
entriesToRemoveOnCommit.put(key, new RemoveEntry(delegate, key));
return delegate.getObject(key);
}
@Override
public void clear() {
reset();
clearOnCommit = true;
}
public void commit() {
if (clearOnCommit) {
// 当提交事务时需要先清空,则清空缓存
delegate.clear();
} else {
// 应用移除键值对操作
for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
entry.commit();
}
}
// 应用添加键值对操作
for (AddEntry entry : entriesToAddOnCommit.values()) {
entry.commit();
}
reset();
}
public void rollback() {
reset();
}
private void reset() {
clearOnCommit = false;
entriesToRemoveOnCommit.clear();
entriesToAddOnCommit.clear();
}
private static class AddEntry {
private Cache cache;
private Object key;
private Object value;
public AddEntry(Cache cache, Object key, Object value) {
this.cache = cache;
this.key = key;
this.value = value;
}
public void commit() {
// 提交的时候,才真正放入缓存
cache.putObject(key, value);
}
}
private static class RemoveEntry {
private Cache cache;
private Object key;
public RemoveEntry(Cache cache, Object key) {
this.cache = cache;
this.key = key;
}
public void commit() {
// 提交的时候才真正从缓存中移除
cache.removeObject(key);
}
}
}
SerializedCache
序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的Copy,用于保存线程安全。
public class SerializedCache implements Cache {
// 省略部分代码
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
// 先序列化后再存放到缓存中
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
// 不为空,则反序列化,生成一份Copy
return object == null ? null : deserialize((byte[]) object);
}
private byte[] serialize(Serializable value) {
try {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
oos.close();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
private Serializable deserialize(byte[] value) {
Serializable result;
try {
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis);
result = (Serializable) ois.readObject();
ois.close();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
}
public static class CustomObjectInputStream extends ObjectInputStream {
public CustomObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// 此方法只有在待序列化的类第一次序列化的时候才会被调用
// 遍历所支持的ClassLoader,加载对应的Class
return Resources.classForName(desc.getName());
}
}
}
MyBatis源码分析(3)—— Cache接口以及实现的更多相关文章
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
- Mybatis源码分析之Cache一级缓存原理(四)
之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...
- mybatis源码分析之04Mapper接口的动态代理
在工作中,使用mybatis操作数据库,只需要提供一个接口类,定义一些方法,然后调用接口里面的方法就可以CRUD,感觉是牛了一逼! 该篇就是记录一下,mybatis是如何完成这波骚操作的,即分析我们测 ...
- MyBatis 源码分析——生成Statement接口实例
JDBC的知识对于JAVA开发人员来讲在简单不过的知识了.PreparedStatement的作用更是胸有成竹.我们最常见用到有俩个方法:executeQuery方法和executeUpdate方法. ...
- MyBatis源码分析(4)—— Cache构建以及应用
@(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...
- 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 【MyBatis源码分析】select源码分析及小结
示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...
- Mybatis源码分析-BaseExecutor
根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...
随机推荐
- SQL中select与set的区别-转载
下表列出 SET 与 SELECT 的区别 SELECT SET 同时对多个变量同时赋值时 支持 不支持 表达式返回多个值时 将返回的最后一个值赋给变量 出错 表达式未返回值时 变量保持原值 变量 ...
- mysq l错误Table ‘./mysql/proc’ is marked as crashed and should be repaired
续上一篇,解决了上一篇中的问题后,启动成功,但是在数据库中操作会存在一些问题,一些操作报一下异常: Table './mysql/proc' is marked as crashed and shou ...
- Java 加解密 AES DES TripleDes
package xxx.common.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypt ...
- js中判断对象具体类型
大家可能知道js中判断对象类型可以用typeof来判断.看下面的情况 <script> alert(typeof 1);//number alert(typeof "2" ...
- POJ 2739. Sum of Consecutive Prime Numbers
Sum of Consecutive Prime Numbers Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 20050 ...
- Android LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)的参数理解
方法inflate(int resource, ViewGroup root, boolean attachToRoot) 中 第一个参数传入布局的资源ID,生成fragment视图,第二个参数是视图 ...
- 三维网格精简算法(Quadric Error Metrics)附源码
在计算机图形应用中,为了尽可能真实呈现虚拟物体,往往需要高精度的三维模型.然而,模型的复杂性直接关系到它的计算成本,因此高精度的模型在几何运算时并不是必须的,取而代之的是一个相对简化的三维模型,那么如 ...
- BZOJ 2820: YY的GCD [莫比乌斯反演]【学习笔记】
2820: YY的GCD Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 1624 Solved: 853[Submit][Status][Discu ...
- NPOI操作EXCEL(四)——反射机制批量导出excel文件
前面我们已经实现了反射机制进行excel表格数据的解析,既然有上传就得有下载,我们再来写一个通用的导出方法,利用反射机制实现对系统所有数据列表的筛选结果导出excel功能. 我们来构想一下这样一个画面 ...
- 扩展RadioButtonListFor和CheckBoxListFor
在我们做正常的MVC的开发中,一些基本的控件已经够用了,但是有时候我们需要用到库里面没有的一些控件,比如RadioButtonListFor和CheckBoxListFor这类的列表控件,在MVC库里 ...