mybatis的缓存机制源码分析之一级缓存解析(代码片段)

犀牛饲养员 犀牛饲养员     2023-01-08     249

关键词:

文章目录

引言

本篇源码解析基于mybatis 3.5.8版本。

MyBatis 中的缓存指的是 MyBatis 在执行一次SQL查询时,在满足一定的条件下,会把这个sql和对应的查询结果缓存起来。当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是请求到数据库。当然如果中间有更新操作,缓存会失效。

MyBatis中的缓存分为一级缓存和二级缓存,一级缓存又被称为 SqlSession 级别的缓存,二级缓存又被称为表级缓存。通俗的说,一级缓存是本次会话有效,二级缓存可以跨越多个会话共享缓存。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

一级缓存开启的情况下,查询的时序图如下:

二级缓存也是类似的机制。

正文

根据上面的调用时序图我们可以看到mybatis是通过SqlSession统一对外的,SqlSession是接口,有个默认实现类:DefaultSqlSession。来看下这个类。

public class DefaultSqlSession implements SqlSession 

  private final Configuration configuration;
  //每个SqlSession中持有了Executor
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;
  ...

DefaultSqlSession持有Executor的引用,事实上对sqlSession的操作都是委托给这个Executor进行的,比如select方法,最终会调用selectList方法:

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) 
    try 
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
     catch (Exception e) 
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     finally 
      ErrorContext.instance().reset();
    
  

Executor是个接口,有个默认的抽象类实现BaseExecutor,这个抽象类持有一个名为``PerpetualCache`的引用,这是个本地缓存的实现类,缓存就是通过这个类来管理的。

public abstract class BaseExecutor implements Executor 

  ...
  //本地缓存实现类
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;
  ...

到这里,我们总结下提到的这几个类的关系:

前面提到BaseExecutor是个抽象类,定义若干抽象方法,方法如下:

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

在执行的时候,把具体的操作委托给对应的实现类进行执行。有几个实现类,为Executor赋予了不同的能力,如下图所示:

BatchExecutor专门用于执行批量sql操作。而SimpleExecutor用于简单sql场景。至于选择哪个,是配置决定的:

  <setting name="defaultExecutorType" value="SIMPLE"/>

既然DefaultSqlSession持有Executor的引用,它是什么时候初始化的呢?答案是在org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession调用的时候,如下:

private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) 
    try 
      ....
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
     catch (Exception e) 
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
     finally 
      ErrorContext.instance().reset();
    
  

继续跟下去,org.apache.ibatis.session.Configuration#newExecutor

public Executor newExecutor(Transaction transaction, ExecutorType executorType) 
    //默认的executor类型是SIMPLE
    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);
    
    //如果二级缓存开关开启的话,是使用CahingExecutor装饰BaseExecutor的子类
    if (cacheEnabled) 
      executor = new CachingExecutor(executor);
    
    /**
     * 插件化处理
     */
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  

大部分时候,我们使用的是SimpleExecutor,注意二级缓存如果开关打开使用的是CachingExecutor,我们下篇文章再来分析二级缓存。 插件化处理那里,后面也会有专门的文章进行解析。

来继续看看PerpetualCache,BaseExecutor持有它的引用对缓存进行管理。查询的逻辑如下:

@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.");
    
    if (queryStack == 0 && ms.isFlushCacheRequired()) 
      clearLocalCache();
    
    List<E> list;
    try 
      queryStack++;
      //先从缓存查询
      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--;
    
    ...

代码逻辑比较清晰,先查缓冲,没有命中就查数据库。PerpetualCache其实是持有一个map进行本地缓存。然后查询db后再更新cache:

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;
  

public class PerpetualCache implements Cache 

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();
  ...

Cache接口有几个实现类,结构如下:

这些不同的实现类彼此通过装饰器模式互相装饰,实现功能的互补。比如说,

public class LruCache implements Cache 

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;
...

初始化的时候可以传入一个其它cache实现类的,复用这个类的功能。

LruCache cache = new LruCache(new PerpetualCache("default"));
    cache.setSize(5);
    for (int i = 0; i < 5; i++) 
      cache.putObject(i, i);
    
    ...

具体的每个实现类不是本文的重点,这里不表。

一级缓存就写到这里吧。


参考

  • https://tech.meituan.com/2018/01/19/mybatis-cache.html

mybatis源码分析-缓存原理(代码片段)

...ache3.CacheKey4.一级缓存5.二级缓存6.总结参考序号内容链接1MyBatis源码分析-MyBatis入门https://thinkwon.blog.csdn.net/article/details/1148088522MyBatis源码分析-配置文件解析过程https://thinkwon.blog.csdn.net/article/details/1148089623MyBatis源码分析-映射文件... 查看详情

mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)

...不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机制源码分析7.Mybatis的一级缓存机制源码分析图解总结8.一级缓存什么时候被清空?9.一级缓存key是什... 查看详情

mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)

...不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机制源码分析7.Mybatis的一级缓存机制源码分析图解总结8.一级缓存什么时候被清空?9.一级缓存key是什... 查看详情

mybatis源码分析之06二级缓存

上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的。通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置<cacheeviction="LRU"type="qinfeng.zheng.RedisCache"/> MYBATIS源码分析之02... 查看详情

mybatis源码分析五mybatis的缓存

五、MyBatis缓存文章目录五、MyBatis缓存缓存的概念与应用缓存的概念开发一个简单的缓存MyBatis中的缓存设计自定义一个Cache实现类MyBatis中的Cache实现类PerpetualCache装饰器CacheCache如何在MyBatis运行过程应用MyBatis缓存的二层体系一级... 查看详情

mybatis源码分析五mybatis的缓存

五、MyBatis缓存文章目录五、MyBatis缓存缓存的概念与应用缓存的概念开发一个简单的缓存MyBatis中的缓存设计自定义一个Cache实现类MyBatis中的Cache实现类PerpetualCache装饰器CacheCache如何在MyBatis运行过程应用MyBatis缓存的二层体系一级... 查看详情

mybatis源码分析-插件机制(代码片段)

...3.2执行插件逻辑4.实现一个分页插件5.总结序号内容链接1MyBatis源码分析-MyBatis入门https://thinkwon.blog.csdn.net/article/details/1148088522MyBatis源码分析-配置文件解析过程https://thinkwon.blog.csdn.net/article/details/1148089623MyBatis源码分析-映射文件解... 查看详情

mybatis从入门到精通—源码剖析之二级缓存细节(代码片段)

⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis⾸先会查询⼆级缓存,若⼆级缓存未命中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。⼆级缓存------》⼀级缓存------》数据库与⼀级缓存不同,⼆级缓存和具体... 查看详情

源码级mybatis缓存策略(一级和二级缓存)(代码片段)

...们可以避免频繁的与数据库进行交互,进而提高响应速度MyBatis也提供了对缓存的支持,分为一级缓存和二级缓存,可以通过下图来理解:①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一... 查看详情

android:深入剖析图片加载库glide缓存功能(源码分析)

参考技术AGlide需要缓存的图片资源分为两类:Glide的缓存机制使得Glide具备非常好的图片缓存效果,从而使得具备较高的图片加载效率。下面,我将根据Glide缓存流程中的每个步骤进行源码分析。至此,Glide的图片缓存Key生成完毕... 查看详情

mybatis缓存的使用和源码分析

Mybatis缓存使用在Mybatis中缓存分为一级缓存和二级缓存,二级缓存又称为全局缓存,默认一级缓存和二级缓存都是开启的,只是二级缓存的使用需要配置才能生效,在Mybatis中一级缓存是SqlSession级别也就是会话级别的,而二级缓... 查看详情

mybatis-缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。–1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本... 查看详情

mybatis-缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。 MyBatis系统中默认定义了两级缓存。 一级缓存和二级缓存。 一级缓存:(本地缓存):SqlSession级别的缓存,一级缓存是一致开... 查看详情

ssm框架mybatis笔记---表之间的关联关系;mybatis事务;mybatis缓存机制;orm概述

MyBatis框架提供两级缓存,一级缓存和二级缓存,默认开启一级缓存。缓存就是为了提交查询效率MyBatis框架提供两级缓存,一级缓存和二级缓存,默认开启一级缓存。缓存就是为了提交查询效率 查看详情

mybatis之缓存机制

1、缓存机制的简单介绍:    a、MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。        b、MyBatis系统中默认定义了两级缓存:&... 查看详情

通过源码分析mybatis的缓存

  看了通过源码分析MyBatis的缓存这篇文章后,自己跟着源码过了一遍,对mybatis的一级缓存和二级缓存有了更清楚的认识。  一级缓存是SqlSession级别的,同一个sqlSession在第二次执行一个相同参数的select语句并且第一次执行... 查看详情

mybatis:缓存机制

...缓存,减少与数据库交互次数,减少系统开销,提高效率MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)二级缓存需要手动开启和配置,他 查看详情

《深入理解mybatis原理4》mybatis缓存机制的设计与实现

《深入理解mybatis原理》MyBatis缓存机制的设计与实现本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存机制中的方方面面展开讨论。MyBatis将数据缓存设计成两级结... 查看详情