带你彻底搞懂MyBatis的底层实现之缓存模块(Cache)-吊打面试官必备技能

}

@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();

}

@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;

// 只关心ID

return getId().equals(otherCache.getId());

}

@Override

public int hashCode() {

if (getId() == null) {

throw new CacheException(“Cache instances require an ID.”);

}

// 只关心ID

return getId().hashCode();

}

}

然后我们可以来看看cache.decorators包下提供的装饰器。他们都实现了Cache接口。这些装饰器都在PerpetualCache的基础上提供了一些额外的功能,通过多个组合实现一些特殊的需求。

3 BlockingCache


通过名称我们能看出来是一个阻塞同步的缓存,它保证只有一个线程到缓存中查找指定的key对应的数据。

public class BlockingCache implements Cache {

private long timeout; // 阻塞超时时长

private final Cache delegate; // 被装饰的底层 Cache 对象

// 每个key 都有对象的 ReentrantLock 对象

private final ConcurrentHashMap<Object, ReentrantLock> locks;

public BlockingCache(Cache delegate) {

// 被装饰的 Cache 对象

this.delegate = delegate;

this.locks = new ConcurrentHashMap<>();

}

@Override

public String getId() {

return delegate.getId();

}

@Override

public int getSize() {

return delegate.getSize();

}

@Override

public void putObject(Object key, Object value) {

try {

// 执行 被装饰的 Cache 中的方法

delegate.putObject(key, value);

} finally {

// 释放锁

releaseLock(key);

}

}

@Override

public Object getObject(Object key) {

acquireLock(key); // 获取锁

Object value = delegate.getObject(key); // 获取缓存数据

if (value != null) { // 有数据就释放掉锁,否则继续持有锁

releaseLock(key);

}

return value;

}

@Override

public Object removeObject(Object key) {

// despite of its name, this method is called only to release locks

releaseLock(key);

return null;

}

@Override

public void clear() {

delegate.clear();

}

private ReentrantLock getLockForKey(Object key) {

return locks.computeIfAbsent(key, k -> new ReentrantLock());

}

private void acquireLock(Object 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 void releaseLock(Object key) {

ReentrantLock lock = locks.get(key);

if (lock.isHeldByCurrentThread()) {

lock.unlock();

}

}

public long getTimeout() {

return timeout;

}

public void setTimeout(long timeout) {

this.timeout = timeout;

}

}

通过源码我们能够发现,BlockingCache本质上就是在我们操作缓存数据的前后通过 ReentrantLock对象来实现了加锁和解锁操作。其他的具体实现类,大家可以自行查阅

| 缓存实现类 | 描述 | 作用 | 装饰条件 |

| — | — | — | — |

| 基本缓存 | 缓存基本实现类 | 默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类 | 无 |

| LruCache | LRU策略的缓存 | 当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use) | eviction=“LRU”(默认) |

| FifoCache | FIFO策略的缓存 | 当缓存到达上限时候,删除最先入队的缓存 | eviction=“FIFO” |

| SoftCacheWeakCache | 带清理策略的缓存 | 通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference | eviction="SOFT"eviction=“WEAK” |

| LoggingCache | 带日志功能的缓存 | 比如:输出缓存命中率 | 基本 |

| SynchronizedCache | 同步缓存 | 基于synchronized关键字实现,解决并发问题 | 基本 |

| BlockingCache | 阻塞缓存 | 通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现 | blocking=true |

| SerializedCache | 支持序列化的缓存 | 将对象序列化以后存到缓存中,取出时反序列化 | readOnly=false(默认) |

| ScheduledCache | 定时调度的缓存 | 在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存 | flushInterval不为空 |

| TransactionalCache | 事务缓存 | 在二级缓存中使用,可一次存入多个缓存,移除多个缓存 | 在TransactionalCacheManager中用Map维护对应关系 |

4 缓存的应用


4.1 缓存对应的初始化

在Configuration初始化的时候会为我们的各种Cache实现注册对应的别名

在这里插入图片描述

在解析settings标签的时候,设置的默认值有如下

在这里插入图片描述

cacheEnabled默认为true,localCacheScope默认为 SESSION

在解析映射文件的时候会解析我们相关的cache标签

在这里插入图片描述

然后解析映射文件的cache标签后会在Configuration对象中添加对应的数据在

private void cacheElement(XNode context) {

// 只有 cache 标签不为空才解析

if (context != null) {

String type = context.getStringAttribute(“type”, “PERPETUAL”);

Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);

String eviction = context.getStringAttribute(“eviction”, “LRU”);

Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);

Long flushInterval = context.getLongAttribute(“flushInterval”);

Integer size = context.getIntAttribute(“size”);

boolean readWrite = !context.getBooleanAttribute(“readOnly”, false);

boolean blocking = context.getBooleanAttribute(“blocking”, false);

Properties props = context.getChildrenAsProperties();

builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);

}

}

继续在这里插入图片描述

然后我们可以发现 如果存储 cache 标签,那么对应的 Cache对象会被保存在 currentCache 属性中。在这里插入图片描述

进而在 Cache 对象 保存在了 MapperStatement 对象的 cache 属性中。

然后我们再看看openSession的时候又做了哪些操作,在创建对应的执行器的时候会有缓存的操作

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

executorType = executorType == null ? defaultExecutorType : executorType;

executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

Executor executor;

if (ExecutorType.BATCH == executorType) {

executor = new BatchExecutor(this, transaction);

} else if (ExecutorType.REUSE == executorType) {

executor = new ReuseExecutor(this, transaction);

} else {

// 默认 SimpleExecutor

executor = new SimpleExecutor(this, transaction);

}

// 二级缓存开关,settings 中的 cacheEnabled 默认是 true

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

// 植入插件的逻辑,至此,四大对象已经全部拦截完毕

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

也就是如果 cacheEnabled 为 true 就会通过 CachingExecutor 来装饰executor 对象,然后就是在执行SQL操作的时候会涉及到缓存的具体使用。这个就分为一级缓存和二级缓存,这个我们来分别介绍

4.2 一级缓存

一级缓存也叫本地缓存(Local Cache),MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置(如果要关闭,localCacheScope设置为STATEMENT)。在BaseExecutor对象的query方法中有关闭一级缓存的逻辑

在这里插入图片描述

然后我们需要考虑下在一级缓存中的 PerpetualCache 对象在哪创建的,因为一级缓存是Session级别的缓存,肯定需要在Session范围内创建,其实PerpetualCache的实例化是在BaseExecutor的构造方法中创建的

protected BaseExecutor(Configuration configuration, Transaction transaction) {

this.transaction = transaction;

this.deferredLoads = new ConcurrentLinkedQueue<>();

this.localCache = new PerpetualCache(“LocalCache”);

this.localOutputParameterCache = new PerpetualCache(“LocalOutputParameterCache”);

this.closed = false;

this.configuration = configuration;

this.wrapper = this;

}

在这里插入图片描述

一级缓存的具体实现也是在BaseExecutor的query方法中来实现的

public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

// 异常体系之 ErrorContext

ErrorContext.instance().resource(ms.getResource()).activity(“executing a query”).object(ms.getId());

if (closed) {

throw new ExecutorException(“Executor was closed.”);

}

if (queryStack == 0 && ms.isFlushCacheRequired()) {

// flushCache="true"时,即使是查询,也清空一级缓存

clearLocalCache();

}

List list;

try {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例

MyBatis答案解析
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例
[外链图片转存中…(img-H9AB7qIo-1712506507025)]

[外链图片转存中…(img-mjmoVPmx-1712506507025)]
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

大家看完有什么不懂的可以在下方留言讨论也可以关注。

觉得文章对你有帮助的话记得关注我点个赞支持一下!

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值