『手撕 Mybatis 源码』10 - 一级缓存

news2024/9/21 2:48:05

一级缓存

概述

在这里插入图片描述

  1. 一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 SqlSession 之间的缓存数据区域(HashMap)是互相不影响的
  2. 二级缓存是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的sql语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession
  3. 首先新增测试用例 firstLevelCacheTest,然后同时会发现如果使用(增删改)操作,不管是否已经 commit(),都会清理一级缓存
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession()执行逻辑是什么?
    // 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 发起第一次查询,查询ID为1的用户
    User user1 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();

    // 发起第二次查询,查询ID为1的用户
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    System.out.println(user1 == user2);
    System.out.println(user1);
    System.out.println(user2);

    sqlSession.close();
  }
}

源码分析

  1. 首先我们主要关注下面的的流程,看看一级缓存的行为,首先执行一次 sqlSession.selectOne() 查询,然后执行一次更新语句,再次执行一次查询
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    // 发起第一次查询,查询ID为1的用户  
    User user1 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();

    // 发起第二次查询,查询ID为1的用户
    User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
   ...
  }
}
  1. 第一次调用 sqlSession.selectOne() 时,将会嵌套调用 selectList() 方法,得到 MappedStatement 后,使用 Executor 来执行查询
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 1. 根据传入的 statementId 即 user.findUserById,获取 MappedStatement 对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 2. 调用执行器的查询方法
      // wrapCollection(parameter)是用来装饰集合或者数组参数
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. Executor 实际使用的是 CachingExecutor,执行前,先生成 CacheKey,然后委托 SimpleExecutor 进行查询
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  //第一步
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 1. 生成缓存Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    ...
    // 2. 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  1. SimpleExecutor 首先从一级缓存 localCache(就是 PerpetualCache,一个 Map)拿取数据,如果获取不了数据就会从数据库查询结果
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  @Override
  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.");
    }
    // 如果配置了 flushCacheRequired为 true,则会在执行器执行之前就清空本地一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清空缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 查询堆栈 + 1
      queryStack++;
      // 1. 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 已有缓存结果,则处理本地缓存结果输出参数(只有存储过程会走)
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 2. 没有缓存结果,则从数据库查询结果
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 查询堆栈数 -1
      queryStack--;
    }
    ...
    return list;
  }
}
  1. 查询数据的时候,首先会在一级缓存 localCache 插入占位符,然后从数据库查出数据后,将查询数据存入缓存中
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list; // key:1463130193:-1799038108:com.itheima.mapper.UserMapper.findByCondition:0:2147483647:SELECT id, name FROM  user WHERE id = ?:1:development
    // 1. 首先向本地缓存中存入一个 ExecutionPlaceholder 的枚举类占位 value
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 2. 执行doQuery方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 3. 执行完成移除这个key
      localCache.removeObject(key);
    }
    // 4. 查询结果存入缓存中
    localCache.putObject(key, list);
    // 如果 MappedStatement 的类型为CALLABLE,则向 localOutputParameterCache 缓存中存入 value 为 parameter 的缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
}
  1. 查询到数据后,轮到执行更新语句
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    // 更新操作(通过增删改执行操作,会清除缓存)
    User user = new User();
    user.setId(1L);
    user.setName("tom");
    sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession.commit();
   ...
  }
}
  1. 更新语句的执行同样创建好 MappedStatement 后,由 DefaultSqlSession 交由 CachingExecutor 执行更新操作
public class DefaultSqlSession implements SqlSession {
  ...
  private final Executor executor;
  ...
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 1. 执行更新
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 再委派 SimpleExecutor 执行更新
public class CachingExecutor implements Executor {
  ...
  private final Executor delegate;
  ...
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    // 1. 执行更新
    return delegate.update(ms, parameterObject);
  }
}
  1. SimpleExecutor 执行更新前,就会先去清除一级缓存,不管是否需要 commit(),然后再执行更新操作
public abstract class BaseExecutor implements Executor {
  ...
  protected PerpetualCache localCache;
  ...
  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 1. 先清理一级缓存
    clearLocalCache();
    // 2. 再执行更新操作
    return doUpdate(ms, parameter);
  }
  
  @Override
  public void clearLocalCache() {
    if (!closed) {
      // 1.1 清理一级缓存
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
}
  1. 从一级缓存 PerpetualCache 的清理,就完全可以看到,一级缓存就是个 Map 对象保存
public class PerpetualCache implements Cache {

  private final String id;
  private final Map<Object, Object> cache = new HashMap<>();
  ...
  @Override
  public void clear() {
    cache.clear();
  }
}
  1. 更新完数据后,然后执行 commit() 操作
public class CacheTest {

  /**
   * 测试一级缓存
   */
  @Test
  public void firstLevelCacheTest() throws IOException {
    ...
    sqlSession.commit();
   ...
  }
}
  1. DefaultSqlSession 再次委托 CachingExecutor 执行 commit() 方法
public class DefaultSqlSession implements SqlSession {

  private final Executor executor;
  ...
  @Override
  public void commit() {
    commit(false);
  }
  @Override
  public void commit(boolean force) {
    try {
      // 1. 执行 commit() 方法
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 则委派 SimpleExecutor 来执行 commit() 方法
public class CachingExecutor implements Executor {
  
  private final Executor delegate;
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    // 1. 执行提交操作
    delegate.commit(required);
    tcm.commit();
  }
}
  1. SimpleExecutor 又会再次清理一级缓存,最后才会执行 commit() 方法
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 1. 一级缓存清理
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }
  
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
}
  1. 更新操作后,再一次执行查询 selectOne() 的流程,但由于一级缓存清空了,所以就没办法从一级缓存获取数据,又会再次从数据库中查询,然后存入一级缓存中。具体流程看回上面的查询流程
  2. 总结
    在这里插入图片描述

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

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

相关文章

hive基于新浪微博的日志数据分析——项目及源码

有需要本项目的全套资源资源以及部署服务可以私信博主&#xff01;&#xff01;&#xff01; 本系统旨在对新浪微博日志进行大数据分析&#xff0c;探究用户行为、内容传播、移动设备等多个方面的特征和趋势&#xff0c;为企业和个人的营销策略、产品设计、用户服务等提供有益的…

PN结、二极管、三极管、三极管放大电路、上拉电路/下拉电路

1、N型参杂 与 P型参杂 B站 视频地址 &#xff1a;https://www.bilibili.com/video/BV1fB4y147Gn 1&#xff09;N型参杂 &#xff08;N型半导体&#xff09; &#xff1a; 4价硅 参杂 5价麟&#xff0c;多一个自由负电子&#xff08;带负电&#xff09; 2&#xff09;P型参杂…

Linux性能学习(4.2):网络_为什么MTU是1500

文章目录 1 基本概念2 为什么MTU是15003 有效载荷最大是1500吗4 Linux下如何修改MTU 参考资料&#xff1a; 1. RFC894 2. 什么是MTU&#xff08;Maximum Transmission Unit&#xff09;&#xff1f; 1 基本概念 Maximum Transmission Unit&#xff0c;缩写MTU&#xff0c;即…

Python爬虫:Scrapy框架

&#x1f680;Python爬虫&#xff1a;Scrapy框架 &#x1f577;️ Scrapy介绍&#x1f4e6; Scrapy框架&#x1f4c1; Scrapy项目&#x1f50d; 创建爬虫过程&#x1f578;️ 页面分析&#x1f4d1; 提取信息&#x1f389; 完整代码&#x1f4dd; 结语 在本篇博文中&#xff0c…

C++6.29思维,作业

有以下类定义&#xff0c;按要求实现剩余功能 #include <iostream> using namespace std;class Person { private:int age;int *p; public://无参构造Person():p(new int(89)){age 18;cout << "无参构造" << endl;}//有参构造Person(int age,int …

docker的容器

首先要关闭防火墙,不然会阻止连接 查询防火墙状态 systemctl status firewalld 如果是running的状态要关闭一下 关闭防火墙 systemctl stop firewalld 禁用防火墙(禁止开机启动) systemctl disable firewalld 容器的创建语句: docker run …

第11节 跟上板块轮动的节奏

板块 文章目录 板块什么是板块板块的分类板块的轮动 板块相关接口本节课任务 什么是板块 股票板块是一些具有相同特征的股票的集合&#xff0c;命名通常也会简单明了的直接按照特征命名。例如沪深300板块&#xff0c;蓝筹板块。对上市公司进行“分班”不论是对于企业还是对于投…

Leetcode-每日一题【148.排序链表】

题目 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3]输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0]输出&#xff1a;[-1,0,3,4,5] 示例 3&#xff1…

VC++多文档项目同时显示多个文本文件

VC6新建一个多文档工程&#xff1b;工程名为txt&#xff1b;生成的类如下&#xff1b;与单文档项目相比多了一个ChildFrm&#xff1b; 在类向导为Doc类添加OnOpenDocument函数&#xff1b; 此时生成的OnOpenDocument()函数如下&#xff1b; BOOL CTxtDoc::OnOpenDocument(LPCT…

Python基础 —— 条件语句

考虑了好久&#xff0c;不知道是先写条件循环&#xff0c;还是先写数据类型,因为如果先写条件循环的话&#xff0c;要涉及到数据类型的内容&#xff1b;先写数据类型的话&#xff0c;又要设计到条件循环的内容…纠结一番后&#xff0c;决定还是先说条件循环&#xff0c;再在数据…

chatgpt赋能python:用Python抓取数据:提高SEO的关键

用Python抓取数据&#xff1a;提高SEO的关键 在数字化时代&#xff0c;数据已经变成了最宝贵的财富之一。然而&#xff0c;对于企业和网站管理者来说&#xff0c;数据仅仅是有价值的当它被收集和转化成行动中存在的信息。这时&#xff0c;Python成为了一个有用的工具&#xff…

基于VORS、CCDM模型、GeoDetector、GWR模型集成技术在城镇化与生态系统健康空间关系分析及影响效应中的应用

城市群是一国经济发展水平的象征&#xff0c;也是一国经济发展到一定阶段的标志&#xff0c;我国城市群建设体量不断增加&#xff0c;将成为全球经济的核心&#xff0c;中国城市群的建设逐步引领全球进入到了21世纪的中国新时代。然而&#xff0c;高速的城镇化发展&#xff0c;…

leetcode题集训 sql

目录 背景步骤175组合两个表&#xff08;多表联查&#xff09;176 177 第n高的薪水&#xff08;Distinct关键字 排序&#xff09;178分数排名 &#xff08;排序 order over关键字&#xff09;179 连续出现的数字 &#xff08;模拟多张表联查&#xff09;181. 超过经理收入的员工…

学号编码:TooY0ung的学院(结构体)

根据66十二位编码规则&#xff0c;用城市代码和出生年编制学号。 【本笔记适合初通算法的 coder 翻阅】 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础…

Web-文件上传漏洞总结

目录 1、常规前端绕过 2、修改文件类型 3、使用 .user.ini 或 .htaccess&#xff08;可能还存在大小写绕过&#xff09; 4、使用字典爆破可行后缀 5、结合文件包含漏洞使用图片马 6、条件竞争 1、常规前端绕过 如下图&#xff0c;在前端存在限制&#xff0c;只能上传图片…

springboot校园点餐小程序

校园点餐系统 springboot校园点餐系统小程序 java校园点餐小程序 技术&#xff1a; 基于springbootvue小程序校园点餐系统的设计与实现 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;…

GPT模型训练实践(2)-Transformer模型工作机制

Transformer 的结构如下&#xff0c;主要由编码器-解码器组成&#xff0c;因为其不需要大量标注数据训练和天然支持并行计算的接口&#xff0c;正在全面取代CNN和RNN&#xff1a; 扩展阅读&#xff1a;What Is a Transformer Model? ​ ​ 其中 编码器中包含自注意力层和前馈…

HCIA回顾笔记整理

OSI 7层参考模式 开放式系统互联参考模型 应用层 抽象语言--> 编码 表示层 编码-->二进制 会话层 提供应用程序地址 -- 无标准 上三层&#xff0c;应用程序加工数据的部分 下四层&#xff0c;数据流层 负责数据传输 传输层 数据分段&#xff08;…

Hive基础知识

1.Hive简介 Hive是由Facebook开源用于解决海量结构化日志的数据统计工具。Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 2.Hive本质 Hive的本质是将HQL转化成MapReduce程序。 Hive处理的数据存储在H…

Git远程操作

目录 分布式版本控制系统 远程仓库 新建远程仓库 管理仓库 issue Pull Request 克隆远程仓库到本地 向远程仓库推送 拉取远程仓库 配置git 忽略特殊文件 给命令配置别名 分布式版本控制系统 我们之前所说的所有内容&#xff0c;都是在本地&#xff0c;也就是只在你的…