百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Mybatis的缓存讲解(mybatis 缓存)

bigegpt 2024-08-01 11:49 12 浏览

前段时间阿粉的一个朋友和阿粉吃饭,在吃饭的时候和阿粉疯狂的吐槽面试官,说面试官问的问题都是些什么问题呀,我一个干了三四年的开发,也不说问点靠谱的,阿粉很好奇,问题问完基础的,一般不都是根据你自己的简历进行提问么?而接下来他说的出来的问题,阿粉表示,阿粉需要继续学习了。


Mybatis是什么?

说到这个,读者大人们肯定心想,阿粉是在开玩笑么?你一个 Java 程序员,你不知道Mybatis是啥么?不就是个持久层的框架么,这东西有啥好说的呢?但是阿粉还是要给大家说。

Mybatis是一个半自动 ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建 statement 等繁杂的过程,我们开发的时候只需要关注如何编写 SQL 语句,而不用关心其他的。

为什么说 Mybatis 是一个半自动 ORM 的框架呢?

ORM,是Object和Relation之间的映射,而Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 框架,而Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

这也是为什么有些面试官在面试初级程序员的时候,很喜欢说,你觉得 Mybatis , 和 Hibernate 都有什么优缺点,为啥你们选择使用的 Mybatis 而不选择使用 Hibernate 呢?

我们都说了 Mybatis是什么了,接下来肯定需要说说面试官都问了什么问题,能让阿粉的朋友变得非常犹豫。

Mybatis的一级、二级缓存是什么你了解么?

Mybatis 的一级缓存

我们先说 Mybatis 的一级缓存,因为这是如果不手动配置,他是自己默认开启的一级缓存,一级缓存只是相对于同一个 SqlSession 而言,参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

当我们面试的时候,说完这个,一般情况下,面试官一定会追问下去,毕竟技术就是要问到你的知识盲区才会停止。

那我们就来画个图表示一下一级缓存

那面试官肯定会说,直接从数据库查不就行了,为啥要一级缓存呢?

当我们使用MyBatis开启一次和数据库的会话时, MyBatis 会创建出一个 SqlSession 对象表示一次与数据库之间的信息传递,在我们执行 SQL 语句的过程中,们可能会反复执行完全相同的查询语句,如果不采取一些措施,我们每一次查询都会查询一次数据库,而如果在极短的时间内做了很多次相同的查询操作,那么这些查询返回的结果很可能相同。

也就是说,如果我们在短时间内,频繁的去执行一条 SQL ,查询返回的结果本来应该是改变了,但是我们查询出来的时候,会出现结果一致的情况,正是为了解决这种问题,也为了减轻数据库的开销,所以 Mybatis 默认开启了一级缓存。

Mybatis 的二级缓存

Mybatis 的二级缓存一般如果你不对他进行设置,他是不会开启的,而二级缓存是什么呢?Mybatis 中的二级缓存实际上就是 mapper 级别的缓存,而这时候肯定会有人说,那么不同之间的 Mapper 是同一个缓存么?

答案是否定的,他不是一个,Mapper 级别的缓存实际上就是相同的 Mapper 使用的是一个二级缓存,但是在二级缓存中,又有多个不同的 SqlSession ,而不同的 Mapper 之间的二级缓存也就是互相不会影响的。

就类似下面的图

这二级缓存是不是就看起来有点意思了?

那怎么能够开启二级缓存呢?

1.MyBatis 配置文件

<settings>
 <setting name = "cacheEnabled" value = "true" />
</settings>

2.MyBatis 要求返回的 POJO 必须是可序列化的

3.Mapper 的 xml 配置文件中加入 标签

既然我们想要了解这个二级缓存,那么必然,我们还得知道它里面的配置都有哪些含义。

我们先从标签看起,然后从源码里面看都有哪些配置信息提供给我们使用:

blocking : 直译就是调度,而在 Mybatis 中,如果缓存中找不到对应的 key ,是否会一直 blocking ,直到有对应的数据进入缓存。

eviction : 缓存回收策略

而缓存回收策略,在源码中是有直接体现的,那么他们分别都对应了什么形式呢?

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
  • PERPETUAL : 选择 PERPETUAL 来命名缓存,暗示这是一个最底层的缓存,数据一旦存储进来,永不清除.好像这种缓存不怎么受待见。
  • FIFO : 先进先出:按对象进入缓存的顺序来移除它们
  • LRU : 最近最少使用的:移除最长时间不被使用的对象。
  • SOFT : 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK : 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

大家虽然看着 PERPETUAL 排在了第一位,但是它可不是默认的,在 Mybatis 的缓存策略里面,默认的是 LRU 。

PERPETUAL :

源代码如下:

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

恩?看着是不是有点眼熟,它怎么就只是包装了 HashMap ? 你还别奇怪,他还真的就是使用的 HashMap ,不得不说,虽然人家是使用的 HashMap ,但是那可是比咱们写的高端多了。

既然使用 HashMap ,那么必然就会有Key,那么他们的Key是怎么设计的?

CacheKey:

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;
  private final int multiplier;
  private int hashcode; //用于表示CacheKey的哈希码
  private long checksum; //总和校验,当出现复合key的时候,分布计算每个key的哈希码,然后求总和
  private int count;//当出现复合key的时候,计算key的总个数
  // 8/21/2017 - Sonarlint flags this as needing to be marked transient.  While true if content is not serializable, this is not always true and thus should not be marked transient.
  private List<Object> updateList;//当出现复合key的时候,保存每个key
  

确实牛逼,至于内部如何初始化,如何进行操作,大家有兴趣的可以去阅读一下源码,导入个源码包,打开自己看一下。

FIFO: 先进先出缓冲淘汰策略

public class FifoCache implements Cache {

  private final Cache delegate; //被装饰的Cache对象
  private final Deque<Object> keyList;//用于记录key 进入缓存的先后顺序
  private int size;//记录了缓存页的上限,超过该值需要清理缓存(FIFO)

  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<>();
    this.size = 1024;
  }

在 FIFO 淘汰策略中使用了 Java 中的 Deque,而 Deque 一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高。

当你看完这个源码的时候,是不是就感觉源码其实也没有那么难看懂,里面都是我们已经掌握好的知识,只不过中间做了一些操作,进行了一些封装。

LRU : 最近最少使用的缓存策略

而 LUR 算法,阿粉之前都说过,如果对这个算法感兴趣的话,文章地址给大家送上,经典的 LRU 算法,你真的了解吗?

而我们需要看的源码则是在 Mybatis 中的源码,

public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;//记录最少被使用的缓存项key

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);//重新设置缓存的大小,会重置KeyMap 字段 如果到达上限 则更新eldestKey
  }
    public void putObject(Object key, Object value) {
      delegate.putObject(key, value);
      // 删除最近未使用的key
      cycleKeyList(key);
    }

SOFT: 基于垃圾回收器状态和软引用规则的对象

在看到基于垃圾回收器的时候,阿粉就已经开始兴奋了,竟然有GC的事情,那还不赶紧看看,这如此高大上(装杯)的事情,来瞅瞅吧!

public class SoftCache implements Cache {
  //在SoftCache 中,最近使用的一部分缓存项不会被GC回收,这就是通过将其value添加到
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  //引用队列,用于记录GC回收的缓存项所对应的SoftEntry对象
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  //底层被修饰的Cache 对象
  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<>();
  }
  
  public void putObject(Object key, Object value) {
      // 清除被GC回收的缓存项
      removeGarbageCollectedItems();
      // 向缓存中添加缓存项
      delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
    }
   public Object getObject(Object key) {
       Object result = null;
       // 查找对应的缓存项
       @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
       SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
       if (softReference != null) {
         result = softReference.get();
         // 已经被GC 回收
         if (result == null) {
           // 从缓存中清除对应的缓存项
           delegate.removeObject(key);
         } else {
           // See #586 (and #335) modifications need more than a read lock 
           synchronized (hardLinksToAvoidGarbageCollection) {
             hardLinksToAvoidGarbageCollection.addFirst(result);
             if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
               hardLinksToAvoidGarbageCollection.removeLast();
             }
           }
         }
       }
       return result;
     }
    public void clear() {
        synchronized (hardLinksToAvoidGarbageCollection) {
          // 清理强引用集合
          hardLinksToAvoidGarbageCollection.clear();
        }
        // 清理被GC回收的缓存项
        removeGarbageCollectedItems();
        delegate.clear();
      }
    //其中指向key的引用是强引用,而指向value的引用是弱引用
    private static class SoftEntry extends SoftReference<Object> {
      private final Object key;
  
      SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
        super(value, garbageCollectionQueue);
        this.key = key;
      }
    }

WEAK : 基于垃圾收集器状态和弱引用规则的对象

public class WeakCache implements Cache {
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;
  private int numberOfHardLinks;

  public WeakCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
  }

WeakCache在实现上与SoftCache几乎相同,只是把引用对象由SoftReference软引用换成了WeakReference弱引用。

在这里阿粉也就不再多说了,关于 Mybatis 的二级缓存,你了解了么?下次遇到面试官问这个的时候,你应该知道怎么成功(装杯)不被打了吧。

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...