• Mybatis源码解析(六):一级缓存和二级缓存的优先级


    Mybatis源码系列文章

    手写源码(了解源码整体流程及重要组件)

    Mybatis源码解析(一):环境搭建

    Mybatis源码解析(二):全局配置文件的解析

    Mybatis源码解析(三):映射配置文件的解析

    Mybatis源码解析(四):sql语句及#{}、${}的解析

    Mybatis源码解析(五):SqlSession会话的创建

    Mybatis源码解析(六):缓存执行器操作流程

    Mybatis源码解析(七):查询数据库主流程

    Mybatis源码解析(八):Mapper代理原理

    Mybatis源码解析(九):插件机制

    Mybatis源码解析(十):一级缓存和二级缓存



    前言

    • 之前篇章讲了配置文件的解析与SqlSession的创建,可以说都是在为执行增删改查操作主流程做铺垫
    • 接下来让我们进入SqlSession的selectOne实现方法

    在这里插入图片描述


    一、会话对象selectOne方法

    selectOne方法

    • SqlSession接口有两个实现类:
    • 方法入参:
      • statement:statementId = “namespace.id”
      • parameter:方法参数,填充带?的sql
    • this.selectList:调用同类的方法,参数传递,从这里可以看出,selectOne方法的实现都交给了selectList,获取到只取第一个值
      • 只有一个值,获取返回即可
      • 如果是空,则返回空
      • 如果大于一个,则报错,因为此方法目的是查询唯一的一个值,结果多个
    @Override
    public <T> T selectOne(String statement, Object parameter) {
      // Popular vote was to return null on 0 results and throw exception on too many.
      List<T> list = this.selectList(statement, parameter);
      if (list.size() == 1) {
        return list.get(0);
      } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) 
        to be returned by selectOne(), but found: " + list.size());
      } else {
        return null;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    selectList方法

    • 三个selectList方法依次向下调用
    • 第一个方法添加默认分页对象调用第二个方法
    • 第二个方法添加结果集处理器空对象调用第三个方法
    • 第三个核心方法:
      • 通过statementId获取MappedStatement(由每个标签中的flushCache属性,刷新二级缓存,默认false
      • ms.isUseCache:标签中的flushCache属性功能一样
      • 先从一级缓存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.");
        }
        // 1. 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          // 1.1. 清空缓存
          clearLocalCache();
        }
        List<E> list;
        try {
          // 2. 查询堆栈 + 1
          queryStack++;
          // 从一级缓存中获取数据
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            // 3.1. 已有缓存结果,则处理本地缓存结果输出参数(存储过程)
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 3.2. 没有缓存结果,则从数据库查询结果
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          // 查询堆栈数 -1
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      进入queryFromDatabase方法

      • 因为延迟加载的原因,这里一级缓存先添加一个占位符
      • 查询结果以后,移除然后再put
      • 具体的查询数据库操作doQuery方法,下一篇文章专门来讲
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 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);
        // 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      总结

      • 一级缓存和二级缓存就是一个Map集合对象:
        • key:Cache对象(statementId、分页参数、带?的sql、参数值、环境id)
        • value:数据库查询结果
      • 一级缓存默认开启,二级缓存需要添加 标签开启
      • 都开启情况下:先从二级缓存获取,没有则从一级缓存获取,还没有则查询数据库(查询结果后先添加到一级缓存,再添加到二级缓存)
  • 相关阅读:
    Apipost 推出IDEA插件一键生成API文档
    磺胺甲恶唑肌SMZ白蛋白纳米粒|磺胺嘧啶豆清SD白蛋白纳米粒|真菌疏水蛋白修饰PLGA载姜黄素纳米粒
    Linux|qtcreator编译可执行程序双击运行
    【智能可视化---02】艺术数据可视化:释放Python AI中Matplotlib的力量!寻觅AI里的Matplotlib,这两篇就够了!
    深度学习手写字符识别:推理过程
    【Linux系统编程:基础IO 下】dup2 实现输出重定向、输入重定向、追加重定向 | 理解磁盘 | 理解文件系统中inode的概念 | 软硬链接
    “医药分离”大背景下,连锁药店如何加速扩张
    SQL Server教程 - T-SQL-DQL(Data Query Language)
    Flask(Jinja2) 服务端模板注入漏洞(SSTI)
    【LeetCode:2736. 最大和查询 | 贪心 + 二分 + 单调栈】
  • 原文地址:https://blog.csdn.net/qq_35512802/article/details/127872970