mybatis源码中一级和二级缓存分析

news2024/9/30 19:42:46

mybatis中,一级缓存的作用域为一个会话内;
二级缓存的作用域为全局的,可在多个会话中使用

1、一级缓存 [此处不讨论开启二级缓存的代码逻辑]

一级缓存的作用域在同一个事物中起作用。真正执行sql的是在 Executor;类图如下;
Executor类图

1.1、生成 Executor对象的逻辑代码如下 Configuration#newExecutor

/**
下面是生成Executor的代码逻辑,如果在没有开启二级缓存的情况下,默认的实现是 
BatchExecutor | ReuseExecutor | SimpleExecutor 这三个对象
*/
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 {
    executor = new SimpleExecutor(this, transaction);
  }
  // 此处时开启二级缓存,在讨论一级缓存的时候假设 cacheEnable = false
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

1.2、执行sql的时候代码逻辑. BaseExecutor#query

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  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()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    // 在执行sql之前,首先会 从locaCache中尝试获取一次;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
        // 如果存在则直接就返回了
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
        // 如果不存在才会进行一次数据库的查询; 
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
  return list;
}

/**
*  获取一级缓存中的数据:  localCache.getObject(key) 真正执行的代码逻辑;即一个 HashMap的本地缓存
*/
public Object getObject(Object key) {
   //private Map<Object, Object> cache = new HashMap<Object, Object>();
  return cache.get(key);
}

1.3、设置结果集到一级缓存中

/**
*  从数据库查询数据后设置到一级缓存中 
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  // 将查询到的结果集添加到一级缓存中
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

1.4、一级缓存失效的时机: 事物提交和事物回滚

/**
* 事物提交会清除一级缓存
*/
public void commit(boolean required) throws SQLException {
  if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
  // 清除一级缓存
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}
/**
* 事物回滚会清除一级缓存
*/
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      // 清除一级缓存
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}
/**
* 删除一级缓存数据
*/
public void clearLocalCache() {
  if (!closed) {
    // 将一级缓存的数据清空
    localCache.clear();
    localOutputParameterCache.clear();
  }
}

2、二级缓存

开启二级缓存需要在mapper.xml中添加 标签 ; 【默认在 config.xml中没有设置 cacheEnabled 的话,值为true】

2.1、生成Executor逻辑.如果开启二级缓存,Executor返回的对象为CachingExecutor ; Configuration#newExecutor

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 {
    executor = new SimpleExecutor(this, transaction);
  }
  // 开启二级缓存
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

2.2、执行sql的时候代码逻辑. CachingExecutor#query


public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
// 此处需要 在mapper.xml中设置了 cache标签才会存在,否则 cache = null
  Cache cache = ms.getCache();
  // 如果设置开启了二级缓存,则先查缓存,缓存中如果有则直接返回,没有从查数据库.此处和一级缓存不一样,一级缓存会直接查到数据就缓存,而二级缓存查到后只是先临时存到了一个缓存中,在事物提交后才会缓存到二级缓存中
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      /**
      * 先从缓存中取,不存在再查数据库.但请注意,此处查到后并没有直接放到二级缓存中,而是放到了一个临时缓存中
      */
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 存放到临时缓存中,并没有真正添加到二级缓存中!!
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
      }
      return list;
    }
  }
  // 如果mapper.xml没有开启二级缓存,则直接走一级缓存的执行逻辑
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
/**
* 获取缓存对象; TransactionalCache#getObject
*/
public Object getObject(Object key) {
  //从二级缓存中尝试获取查询结果集,多个会话在执行同一个select语句的时候,Cache对象是同一个,所以二级缓存的作用域是多个会话中可用
  Object object = delegate.getObject(key);
  if (object == null) {
    entriesMissedInCache.add(key);
  }
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}

2.3、将查询结果设置到二级缓存中


public void commit(boolean required) throws SQLException {
  delegate.commit(required);
  // 新增缓存
  tcm.commit();
}

/**
* TransactionalCacheManager#commit
*/
public void commit() {
  // 将本次会话中涉及到的所有缓存都添加到二级缓存中!
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}

/**
 * 将临时当前会话中的事物提交到二级缓存中TransactionalCache#commit()
 */
public void commit() {
  if (clearOnCommit) {
    delegate.clear();
  }
  // 真正将数据保存到二级缓存的方法
  flushPendingEntries();
  reset();
}

/**
 * 新增数据到二级缓存中
 */
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      // 新增缓存
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}

2.4、二级缓存中的数据移除

默认的二级缓存的Cache的调用顺序如下如所示,真正的移除操作是在 LruCache的put操作中实现的。当缓存Map中的个数 > 1024个则进行移除操作.
默认缓存流向

2.4.1、LruCache#putObject 移除二级缓存中的元素.
public void putObject(Object key, Object value) {
  delegate.putObject(key, value);
  cycleKeyList(key);
}

private void cycleKeyList(Object key) {
  keyMap.put(key, key);
  // 如果二级缓存中的元素个数超过了 1024时,移除最早的一个元素
  if (eldestKey != null) {
    delegate.removeObject(eldestKey);
    eldestKey = null;
  }
}

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

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

相关文章

玩转代码|使用acme.sh在Ubuntu配置Let’s Encrypt免费通配符SSL证书

&#x1f4e3;今日作品&#xff1a;使用acme.sh在Ubuntu配置Let’s Encrypt免费通配符SSL证书&#x1f466; 创作者&#xff1a;Jum朱⏰预计花费&#xff1a;10分钟&#x1f4d6;个人主页&#xff1a; Jum朱博客的个人主页acme.sh 是一款方便,强大的 Lets Encrypt 域名证书申请…

反射内存卡读写测试(RFM2gRead和RFM2gWrite)-- C++

一、函数介绍&#xff1a; 1.1 RFM2gWrite STDRFM2GCALL RFM2gWrite( RFM2GHANDLE rh, RFM2G_UINT32 Offset, void *Buffer, RFM2G_UINT32 Length ); 说明&#xff1a; RFM2gWrite()函数将一个或多个I/O数据缓冲区从应用程序传输到RFM2g节点&#xff0c;从指定的对齐内存偏…

InVEST模型 | 02 InVEST模型Python安装

InVEST在生态系统评估领域有着广泛的应用&#xff0c;由于其交互界面简洁直接&#xff0c;大大降低了模型的使用门槛。但当需要多次、多区域的运算时&#xff0c;手动点击的方法十分耗时费力&#xff0c;针对这样的情况&#xff0c;InVEST团队推出了natcap.invest接口&#xff…

Centos7部署Sonic前后端和Agent 端

前言 1、sonic介绍 Sonic是一款开源、支持分布式部署、在线自动化测试的私有云真机平台&#xff0c;Sonic官网地址 功能特性&#xff1a; Sonic架构&#xff1a; 2、准备工作 ①准备两台设备&#xff0c;并安装Centos系统&#xff0c;设备名称简称&#xff1a;设备1和设…

【Java】Java零基础第一节

Java.java 与 .class关于开发环境 - SDK第一个程序 - Hello worldJava程序理解 - Classes, New, Methods and Type.java 与 .class xxx.java文件&#xff1a; 存储的是人类语言可以看懂的高级语言(Language)&#xff0c;但是计算机不能看懂&#xff1b; xxx.class文件&#xf…

【YOLO V5】代码复现过程

接上篇&#xff0c;讲到如何从mask转成YOLOv5训练需要的txt数据集格式&#xff0c;这篇就在此基础上进行模型训练预测和部署转换吧&#xff01; 目录 1.环境准备 2.YOLO训练 2.1 数据集准备 2.2 data.yaml准备 2.3 yolov5.yaml准备 2.4 训练命令 3.YOLO预测 3.1OLOv5 P…

大猫盘 黑群晖 不用Docker 部署属于自己的聊天工具voceChat

引入 原因很简单我有个大猫盘&#xff0c;自己搞成了黑群&#xff0c;不支持Docker&#xff0c;我自己购买了域名&#xff0c;做了ddns解析&#xff0c;给群晖加了ssl证书&#xff0c;感觉既然数据安全了&#xff0c;服务也在自己家里能不能搭建一个自己的聊天软件&#xff0c…

windows下nvvp的基础使用1

windows下nvvp的基础使用1 cuda编程的重要帮手可视化工具nvvp 本来先写nsignt的使用方式,不过折腾了一会发现没弄得那么明白.先用着nvvp好了,毕竟只是先看书配合着写点简单的cuda代码而已 安装建议 在windows下安装cuda的话,也就那回事,自己可以参考一下搜索引擎 (win10安…

一次漏洞挖掘的简单组合拳

前言&#xff1a; 在最近的wxb举行hw中&#xff0c;同事让我帮他看看一些后台登录站点。尝试了未授权&#xff0c;弱口令皆无果&#xff0c;要么不存在弱口令&#xff0c;要么有验证码&#xff0c;没办法绕过。本文章仅提供一个思路&#xff0c;在hw中更多时候并不推荐尝试这种…

给正在注册或即将注册个体工商户营业执照的你

大家好&#xff0c;我是中国码农摘星人。 欢迎分享/收藏/赞/在看&#xff01; 作为程序员&#xff0c;平时除了主业&#xff0c;发展一些副业再正常不过。为了取得开展业务的合法性、合规性&#xff0c;以及后续的拓展&#xff0c;避免产生不必要的纠纷&#xff0c;这边就得注册…

ChatGPT真的会取代程序员吗?

程序员这两年被碰的瓷儿可不少啊&#xff0c;这架势不像是AI抢了程序员的饭碗&#xff0c;倒像是程序员抢了AI的饭碗一样...... 前两年低代码出来了&#xff0c;你们说程序员要被取代了&#xff0c;惹得大神们一顿输出&#xff1b;去年元宇宙出来了&#xff0c;你们又说程序员…

map相关接口(map接口、HashMap、LinkedHashMap、TreeMap)

Java知识点总结&#xff1a;想看的可以从这里进入 目录8.3、map结构8.3.1、 map接口8.3.2、HashMap8.3.3、LinkedHashMap8.3.4、TreeMap8.3、map结构 8.3.1、 map接口 map的集合是以键值对的形式存在的 (key-value)&#xff0c;每个键只能对应一个值&#xff0c;通常通过键去…

什么表单设计工具能快速提升办公效率?

在信息化快速发展的年代&#xff0c;谁能掌握更先进的技术&#xff0c;谁就能拥有更广阔的发展前景。在以前的办公环境中&#xff0c;传统的表单制作工具占据了主流地位&#xff0c;随着办公自动化的快速发展&#xff0c;传统表单工具的弊端也暴露出来了&#xff0c;采用更先进…

m0n0wall防火墙(10)

实验目的 1、深入理解防火墙的功能和工作原理&#xff1b; 2、熟悉软件防火墙&#xff0c;掌握m0n0防火墙的规则和配置。预备知识 m0n0wall M0n0wall是基于FreeBSD内核开发的免费软件防火墙。m0n0wall提供基于web的配置管理、提供VPN功能、支持DHCP Server、DNS转发、动态DNS…

maddpg 复现过程中遇到的问题

最近在复现论文Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments https://github.com/openai/multiagent-particle-envshttps://github.com/philtabor/Multi-Agent-Deep-Deterministic-Policy-Gradients.gitGitHub - philtabor/Multi-Agent-Deep-Dete…

windows配置c语言编译系统-wingw gcc cmake

前言 笔者在做嵌入式mcu编程的时候&#xff0c;有时候想要验证一下部分代码的功能&#xff0c;需要先编译成bin文件&#xff0c;然后烧录到mcu内执行。每次编译烧录运行耗时较久。于是想到是不是可以在电脑上配置一个c的编译环境&#xff0c;来验证一些与硬件不相干的代码。验…

idea多时编辑多行-winmac都支持

1背景介绍 idea编辑器非常强大&#xff0c;其中一个功能非常优秀&#xff0c;很多程序员也非常喜欢用。这个功能能够大大大提高工作效率-------------多行代码同时编辑 2win 2.1方法1 按住alt鼠标左键上/下拖动即可 这样选中多行后&#xff0c;可以直接多行编辑。 优点&a…

jsp物品找回系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 物品找回系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5 开发&#xff0c;数据库为Mysql&#xff0c;使用j…

一文搞定python语法进阶

前言前面我们已经学习了Python的基础语法&#xff0c;了解了Python的分支结构&#xff0c;也就是选择结构、循环结构以及函数这些具体的框架&#xff0c;还学习了列表、元组、字典、字符串这些Python中特有的数据结构&#xff0c;还用这些语法完成了一个简单的名片管理系统。下…

Java基础-类加载器

写在前面的话&#xff1a; 基础加强包含了&#xff1a; 反射&#xff0c;动态代理&#xff0c;类加载器&#xff0c;xml&#xff0c;注解&#xff0c;日志&#xff0c;单元测试等知识点 其中最难的是反射和动态代理&#xff0c;其他知识点都非常简单 由于B站P数限制&#xff0c…