基于MyBatis二级缓存深入装饰器模式

news2024/11/26 14:37:39

视频地址

学习文档


文章目录

  • 一、示意代码
  • 二、装饰器
  • 三、经典案例—MyBatis二级缓存
    • 1、Cache 标准定义
    • 2、PerpetualCache 基础实现
    • 3、增强实现
      • 3-1、ScheduledCache
      • 3-2、LruCache


先来说说我对装饰器理解:当你有一个基础功能的代码,但你想在不改变原来代码的基础上进行功能增强,并且可以随意组合增强的时候,就可以使用装饰器模式。

我把装饰器模式理解成是一种套娃模式,所谓套娃就是一层套一层,随意组合想怎么套就怎么套,层层嵌套的基础是有一个最初的原型。


一、示意代码


对于个人而言,首先是一个人,其次可以有不同的身份,各种身份可以随意组合。这就可以看成一个装饰器模式的案例。

// 定义接口
interface Human {
    String identity();
}

// 基础实现
class OrdinaryHuman implements Human {
    @Override
    public String identity() {
        return "我是一个人";
    }
}


// 增强实现
class Teacher implements Human {
    protected Human human;

    public teacher(Human human) {
        this.human = human;
    }

    @Override
    public String identity() {
        human.identity();
        return "我是一名老师";
    }
}

// 增强实现
class Father implements Human {
    protected Human human;

    public father(Human human) {
        this.human = human;
    }

    @Override
    public String identity() {
        human.identity();
        return "我是一名父亲";
    }
}
public static void main(String[] args) {
    OrdinaryHuman ordinaryHuman = new OrdinaryHuman();
    Teacher teacher = new Teacher(ordinaryHuman);
    Father father = new Father(teacher);

    father.identity();
}

输出

我是一个人
我是一名老师
我是一名父亲

二、装饰器


GPT的解释
装饰器模式是一种能够在不改变原对象代码的情况下,动态地为对象添加新功能的设计模式。通过将对象包装在装饰器类中,可以透明地、在运行时选择性地、以任意顺序地应用这些功能。最终效果是通过组合不同的装饰器,扩展原对象功能,使系统更灵活可扩展。


想要实现一个装饰器,如下三步:

  1. 要有一个标准定义(Human)
  2. 要有一个基础的实现(OrdinaryHuman)
  3. 每个增强的的实现除了实现标准定义重写对应的方法之外,还要提供一个特殊的构造方法,参数就是接口的某个实现类(基于这个特殊的构造方法,就可以随意组合了)

三、经典案例—MyBatis二级缓存


在这里插入图片描述


  1. Cache 标准定义
  2. PerpetualCache 基础实现
  3. decorators 包下面的就是各种增强实现

注:在现在分布式系统的大环境下,MyBatis的二级缓存几乎没有用武之地,但它确实理解装饰器模式的一个好案例,它的代码很简单,所以格外的好理解


1、Cache 标准定义


cache里面定义了缓存的一些操作

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

2、PerpetualCache 基础实现


PerpetualCache 的实现也很简单,就是一个 HashMap,Value就是缓存的数据,Key 是自定义的 CacheKey

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return this.cache.size();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

    public Object getObject(Object key) {
        return this.cache.get(key);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

CacheKey 并不是装饰器的里面内容,但它的设计挺有意思的,简单来说就是:把当前这次操作的标识,比如 接口全限定名、请求参数、执行器信息 存入 List里面, 基于这些信息去生成唯一的 HashCode

public class CacheKey implements Cloneable, Serializable {
    private static final long serialVersionUID = 1146682552656046210L;
    public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
        public void update(Object object) {
            throw new CacheException("Not allowed to update a null cache key instance.");
        }

        public void updateAll(Object[] objects) {
            throw new CacheException("Not allowed to update a null cache key instance.");
        }
    };
    private static final int DEFAULT_MULTIPLIER = 37;
    private static final int DEFAULT_HASHCODE = 17;
    private final int multiplier;
    private int hashcode;
    private long checksum;
    private int count;
    private List<Object> updateList;

    public CacheKey() {
        this.hashcode = 17;
        this.multiplier = 37;
        this.count = 0;
        this.updateList = new ArrayList();
    }

    public CacheKey(Object[] objects) {
        this();
        this.updateAll(objects);
    }

    public int getUpdateCount() {
        return this.updateList.size();
    }

    public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
        ++this.count;
        this.checksum += (long)baseHashCode;
        baseHashCode *= this.count;
        this.hashcode = this.multiplier * this.hashcode + baseHashCode;
        this.updateList.add(object);
    }

    public void updateAll(Object[] objects) {
        Object[] var2 = objects;
        int var3 = objects.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Object o = var2[var4];
            this.update(o);
        }

    }

    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 (!ArrayUtil.equals(thisObject, thatObject)) {
                        return false;
                    }
                }

                return true;
            }
        }
    }

    public int hashCode() {
        return this.hashcode;
    }

    public String toString() {
        StringJoiner returnValue = new StringJoiner(":");
        returnValue.add(String.valueOf(this.hashcode));
        returnValue.add(String.valueOf(this.checksum));
        Stream var10000 = this.updateList.stream().map(ArrayUtil::toString);
        Objects.requireNonNull(returnValue);
        var10000.forEach(returnValue::add);
        return returnValue.toString();
    }

    public CacheKey clone() throws CloneNotSupportedException {
        CacheKey clonedCacheKey = (CacheKey)super.clone();
        clonedCacheKey.updateList = new ArrayList(this.updateList);
        return clonedCacheKey;
    }
}

3、增强实现


它这个里面的增强实现挺多的,着重看两个有意思的 ScheduledCache、LruCache


3-1、ScheduledCache


ScheduledCache 是一个支持定时清除缓存数据,可以通过 setClearInterval 方法设置清除时间缓存间隔,单位毫秒。

public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
}


清除缓存的原理并没有很复杂,它的代码超级简单, this.lastClear 是上一次清除的时间,只需要在操作缓存的之前先调用下面的代码即可。

private boolean clearWhenStale() {
    if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
        this.clear();
        return true;
    } else {
        return false;
    }
}

全部代码如下:

public class ScheduledCache implements Cache {
    private final Cache delegate;
    protected long clearInterval;
    protected long lastClear;

    public ScheduledCache(Cache delegate) {
        this.delegate = delegate;
        this.clearInterval = TimeUnit.HOURS.toMillis(1L);
        this.lastClear = System.currentTimeMillis();
    }

    public void setClearInterval(long clearInterval) {
        this.clearInterval = clearInterval;
    }

    public String getId() {
        return this.delegate.getId();
    }

    public int getSize() {
        this.clearWhenStale();
        return this.delegate.getSize();
    }

    public void putObject(Object key, Object object) {
        this.clearWhenStale();
        this.delegate.putObject(key, object);
    }

    public Object getObject(Object key) {
        return this.clearWhenStale() ? null : this.delegate.getObject(key);
    }

    public Object removeObject(Object key) {
        this.clearWhenStale();
        return this.delegate.removeObject(key);
    }

    public void clear() {
        this.lastClear = System.currentTimeMillis();
        this.delegate.clear();
    }

    public int hashCode() {
        return this.delegate.hashCode();
    }

    public boolean equals(Object obj) {
        return this.delegate.equals(obj);
    }

    private boolean clearWhenStale() {
        if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
            this.clear();
            return true;
        } else {
            return false;
        }
    }
}

3-2、LruCache


GPT对LRU的解释
LRU(Least Recently Used)是一种缓存淘汰策略,用于管理缓存中的数据项。该策略的基本思想是,当缓存达到容量上限时,选择最近最少被使用的数据项进行淘汰。


LruCache 的代码很少,来一起看看它是如何实现LUR的

  1. eldestKey 最少被使用的一个数据,当超过容量的时候就删除它
  2. 每次添加元素的时候都可能超容,通过 cycleKeyList 方法判断是否需要删除数据
  3. LinkedHashMap 提供了一个删除最后一个元素的接口 removeEldestEntry ,通过它来删除最后一个元素
public class LruCache implements Cache {
    private final Cache delegate;
    private Map<Object, Object> keyMap;
    private Object eldestKey;

    public LruCache(Cache delegate) {
        this.delegate = delegate;
        this.setSize(1024);
    }

    public String getId() {
        return this.delegate.getId();
    }

    public int getSize() {
        return this.delegate.getSize();
    }

    public void setSize(final int size) {
        this.keyMap = new LinkedHashMap<Object, Object>(size, 0.75F, true) {
            private static final long serialVersionUID = 4267176411845948333L;
            
            // eldest 就是链表中最后一个元素
            // 当容量大小超过的时候就让 LruCache.this.eldestKey = eldest.getKey();
            protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
                boolean tooBig = this.size() > size;
                if (tooBig) {
                    LruCache.this.eldestKey = eldest.getKey();
                }

                return tooBig;
            }
        };
    }

    public void putObject(Object key, Object value) {
        this.delegate.putObject(key, value);
        this.cycleKeyList(key);
    }

    public Object getObject(Object key) {
        this.keyMap.get(key);
        return this.delegate.getObject(key);
    }

    public Object removeObject(Object key) {
        return this.delegate.removeObject(key);
    }

    public void clear() {
        this.delegate.clear();
        this.keyMap.clear();
    }

    private void cycleKeyList(Object key) {
        this.keyMap.put(key, key);
        // 如果当前要删除的元素不为空,就删除这个元素
        if (this.eldestKey != null) {
            this.delegate.removeObject(this.eldestKey);
            this.eldestKey = null;
        }

    }
}

基于上面的分析,只需要来看看 removeEldestEntry 方法是在什么时候调用的就好了,往LinkedHashMap添加元素的时候,调用的实际上是 HashMap 的 put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

省去插入元素和扩容的代码,调用了 afterNodeInsertion方法 evict = true

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    // ....
    afterNodeInsertion(evict);
    return null;
}

LinkedHashMap中 afterNodeInsertion 实现,把链表头也是最开始的元素传递给 removeEldestEntry

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1301030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轻量封装WebGPU渲染系统示例<46>- 材质组装管线(MaterialPipeline)灯光、阴影、雾以及多Pass(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/MaterialPipelineMultiPasses.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class MaterialPipelin…

airserver mac 7.27官方破解版2024最新安装激活图文教程

airserver mac 7.27官方破解版是一款好用的airplay投屏工具&#xff0c;可以轻松将ios荧幕镜像&#xff08;airplay&#xff09;至mac上&#xff0c;在mac平台上实现视频、音频、幻灯片等文件资源的接收及投放演示操作&#xff0c;解决iphone或ipad的屏幕录像问题&#xff0c;满…

Leetcode1466. 重新规划路线

Every day a Leetcode 题目来源&#xff1a;1466. 重新规划路线 解法1&#xff1a;深度优先搜索 n 座城市&#xff0c;从 0 到 n-1 编号&#xff0c;其间共有 n-1 条路线。 因此&#xff0c;要想在两座不同城市之间旅行只有唯一一条路线可供选择&#xff08;路线网形成一颗…

实战1-python爬取安全客新闻

一般步骤&#xff1a;确定网站--搭建关系--发送请求--接受响应--筛选数据--保存本地 1.拿到网站首先要查看我们要爬取的目录是否被允许 一般网站都会议/robots.txt目录&#xff0c;告诉你哪些地址可爬&#xff0c;哪些不可爬&#xff0c;以安全客为例子 2. 首先测试在不登录的…

高项备考葵花宝典-项目进度管理核心概念加强记忆

项目进度管理的核心目标是使项目按时完成。 目录 一、待办事项列表 二、看板方法 三、在制品 四、进度计划模型 五、活动清单 六、里程碑清单 七、前导图法 八、资源日历 九、活动历时估算方法 一、待办事项列表 如上图所示&#xff0c;实际工作中需求往往不是一次性全…

【Linux】find . -perm 644 -exec ls -l {} \;

find . -perm 644 -exec ls -l {} ; find 命令使用 -perm 644 条件来查找文件权限为644的文件&#xff0c;然后通过 -exec ls -l {} \; 将这些文件传递给 ls -l 命令来显示详细的文件列表。 find . -perm 644&#xff1a;在当前目录及其子目录中查找文件权限为644的文件。 -e…

力扣每日一题day33[111. 二叉树的最小深度]

给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2示例 2&#xff1a; 输入…

【小白专用】php执行sql脚本 更新23.12.10

可以使用 PHP 的 mysqli 扩展来执行 SQL 脚本。具体步骤如下&#xff1a; 连接到数据库&#xff1b;打开 SQL 脚本文件并读取其中的 SQL 语句&#xff1b;逐条执行 SQL 语句&#xff1b;关闭 SQL 脚本文件&#xff1b;关闭数据库连接。 以下是通过 mysqli 执行 SQL 脚本的示例…

python 画条形图(柱状图)

目录 前言 基础介绍 月度开支的条形图 前言 条形图&#xff08;bar chart&#xff09;&#xff0c;也称为柱状图&#xff0c;是一种以长方形的长度为变量的统计图表&#xff0c;长方形的长度与它所对应的变量数值呈一定比例。 当使用 Python 画条形图时&#xff0c;通常会使…

STM32 标准外设SPL库、硬件抽象层HAL库、低层LL库区别?

1、STM32 之一 HAL库、标准外设库、LL库_ZCShou的博客-CSDN博客_ll库&#xff08;仔细阅读&#xff09; 2、STM32标准外设库、 HAL库、LL库 - King先生 - 博客园 3、STM32 之 HAL库_戈 扬的博客&#xff08;仔细阅读&#xff09; 4、STM32 LL 为什么比 HAL 高效&#xff1…

通信:mqtt学习网址

看这个网址&#xff1a;讲的很详细&#xff0c;后面补实战例子 第一章 - MQTT介绍 MQTT协议中文版 (gitbooks.io)https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/01-Introduction.html

【Matlab算法】多维函数求解的基本概念

多维函数求解的基本概念 多维函数最优化问题最优化算法最优化问题的类型最优化算法的分类常用的多维函数求解方法结语 多维函数 多维函数是指定义在 R n \mathbb{R}^n Rn 上的函数&#xff0c;其中 n n n 是函数的维数。例如&#xff0c; f ( x , y ) x 2 y 2 f(x, y) x^…

DL Homework 10

习题6-1P 推导RNN反向传播算法BPTT. 习题6-2 推导公式(6.40)和公式(6.41)中的梯度 习题6-3 当使用公式(6.50)作为循环神经网络的状态更新公式时&#xff0c; 分析其可能存在梯度爆炸的原因并给出解决方法&#xff0e; 当然&#xff0c;因为我数学比较菜&#xff0c;我看了好半…

Vue3:表格单元格内容由:图标+具体内容 构成

一、背景 在Vue3项目中&#xff0c;想让单元格的内容是由 &#xff1a;图标具体内容组成的&#xff0c;类似以下效果&#xff1a; 二、图标 Element-Plus 可以在Element-Plus里面找是否有符合需求的图标iconfont 如果Element-Plus里面没有符合需求的&#xff0c;也可以在这…

掌握iText:轻松处理PDF文档-基础篇

关于iText iText是一个强大的PDF处理库&#xff0c;可以用于创建、读取和操作PDF文件。它支持PDF表单、加密和签署等操作&#xff0c;同时支持多种字体和编码。maven的中央仓库中的最新版本是5.X&#xff0c;且iText5不是完全免费的&#xff0c;但是基础能力是免费使用的&…

MyBatis 四大核心组件之 StatementHandler 源码解析

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

【设计模式--创建型--建造者模式】

建造者模式 建造者模式概述结构结果优缺点使用场景 将上述案例改为链式调用结果 建造者模式 概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构建&#xff08;由Builder来负责&#xff09;和装配&#xff08;由Direct…

【HarmonyOS开发】拖拽动画的实现

动画的原理是在一个时间段内&#xff0c;多次改变UI外观&#xff0c;由于人眼会产生视觉暂留&#xff0c;所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧&#xff0c;对应一次屏幕刷新&#xff0c;而决定动画流畅度的一个重要指标就是帧率FPS&#xff08;F…

[ 蓝桥杯Web真题 ]-冬奥大抽奖

目录 介绍 准备 目标 规定 思路 知识补充 解法参考 介绍 蓝桥云课庆冬奥需要举行一次抽奖活动&#xff0c;我们一起做一个页面提供给云课冬奥抽奖活动使用。 准备 开始答题前&#xff0c;需要先打开本题的项目代码文件夹&#xff0c;目录结构如下&#xff1a; ├──…

Qt设置类似于qq登录页面

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QWindow> #include <QIcon> #include <QLabel> #include <QMovie> #include <QLineEdit> #include <QPushButton>QT_BEGIN_NAMESPACE namespace Ui { class…