https://blog.csdn.net/zhurhyme/article/details/81064108

对于mybatis的缓存认识一直有一个误区,所以今天写一篇文章帮自己订正一下。mybatis的缓存分一级缓存与二级缓存。下面分别对这两种缓存进行详细解说。
mybatis 的调用链

首先我们需要大致的了解一下mybatis的调用链,然后才能更好的理解mybatis的缓存。
主要的api罗列如下:

public interface SqlSessionFactory {    
  SqlSession openSession();
}

public class DefaultSqlSessionFactory implements SqlSessionFactory {

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);//重点是这个方法
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
}

Configuration中的
    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); //mybatis的插件机制
        return executor;
      }

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
    43
    44
    45
    46
    47

上面的代码大致描述了,我们为了获取一个sqlSession实例的过程。下面的方法为SqlSession的实现类DefaultSqlSession中的一个查询方法.

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

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

从该方法我们可知SqlSession的方法实现全部委托给了Executor实例。Executor接口的类图:

这里写图片描述
mybatis 一级缓存

上面的executor.query如何执行呢?public abstract class BaseExecutor 中的方法给了我们答案.

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

@SuppressWarnings("unchecked")
  @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--;
    }
    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

请注意localCache是什么呢?在BaseExecutor的构造器有这么一句代码: this.localCache = new PerpetualCache(“LocalCache”); 即locaCache为缓存,即mybatis的一级缓存。

localCache生命周期是与SqlSession的生命周期是一样;不同的sqlSession会生成不同的localCache.这就是mybatis的一级缓存,它是与sqlSession息息相关,只能单个sqlSession实例独享。并且默认是开启的,没有开关可以关闭它。但是它的使用非常有局限。所以mybatis才需要在二级缓存,即突破SqlSession范围的缓存。mybatis的二级缓存与我们通常认知的缓存没有区别。
mybatis 的二级缓存

mybatis的二级缓存即通过CachingExecutor来实现。

CachingExecutor 中的query方法代码如下:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache(); //这句是重点
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, 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 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

即当cache不为null时,则从tcm中获取,如果获取不到则再从delegate中获取。
现在关键是tcm.getObject(cache, key);
private final TransactionalCacheManager tcm = new TransactionalCacheManager();

TransactionalCacheManager的部分源代码

public class TransactionalCacheManager {

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
      }

private TransactionalCache getTransactionalCache(Cache cache) {
    /*
    关键代码,它的作用从transactionalCaches 中获取TransactionalCache ,如果没有,则将cache作为参数,重新new一个,这段代码涉及jdk8的新语法及以及新特性,
    不太容易懂,如果有小朋友看不懂,我建议你好好学习一下jdk8
    */  
        return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
      }

}

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

TransactionalCache 部分源码

public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<>();
        this.entriesMissedInCache = new HashSet<>();
      }

@Override
      public Object getObject(Object key) {
        // issue #116
        Object object = delegate.getObject(key);
        if (object == null) {
          entriesMissedInCache.add(key);
        }
        // issue #146
        if (clearOnCommit) {
          return null;
        } else {
          return object;
        }
      }

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

峰回路转

Cache cache = ms.getCache(); //这句是重点

1

即只要MappedStatement ms 自己有cache,则直接使用,这个就是mybatis的二级缓存。关于开启mybatis的二级缓存,则需要在config中配置cacheEnabled 为ture,它的默认值即为true。
另外需要在mapper中配置

<cache />

1

这样就可以最简单的方式使用mybatis的二级缓存了。至于如何扩展mybatis的二级缓存,不在本文之列。
mybatis 二级缓存絮叨

至此mybatis 的二级缓存好像已经说明白了。但是还想在此絮叨一下,原因是为了更好的理解什么是缓存。mybatis的二级缓存cache它是跟在MappedStatement上的。我们设置 cache标签,是以mapper为单位的。即这个mapper文件内的statement 共用这个cache标签。cache它与sqlSession没有任何关系,即不同的sqlSession执行同一个statement就可以共用同一个cache实例了。

那么是否有更大的cache使用范围呢?答案是有的,举个栗子。 部门表被cache;人员表被cache,从上面我们可知,这是两个不同的cache实例。在人员mapper中如何出人员与部门的联合查询,则当部门更新时,会出现局部的数据不一致。如何解决这个问题呢?一种答案是去掉缓存;另一种就是mybatis给我们提供的

<cache-ref >

1

标签,它可以使多个mapper使用同一个cache实例,从而达到缓存数据的一致性(关于缓存数据的一致性,性能,缓存数据范围,在此不讨论)。
---------------------
作者:zhurhyme
来源:CSDN
原文:https://blog.csdn.net/zhurhyme/article/details/81064108
版权声明:本文为博主原创文章,转载请附上博文链接!

mybatis两级缓存原理剖析的更多相关文章

  1. 用guava快速打造两级缓存能力

    首先,咱们都有一共识,即可以使用缓存来提升系统的访问速度! 现如今,分布式缓存这么强大,所以,大部分时候,我们可能都不会去关注本地缓存了! 而在一起高并发的场景,如果我们一味使用nosql式的缓存,如 ...

  2. Spring+ehcache+redis两级缓存

    问题描述 场景:我们的应用系统是分布式集群的,可横向扩展的.应用中某个接口操作满足以下一个或多个条件: 1. 接口运行复杂代价大, 2. 接口返回数据量大, 3. 接口的数据基本不会更改, 4. 接口 ...

  3. Redis+Caffeine两级缓存,让访问速度纵享丝滑

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中, ...

  4. 基于Spring接口,集成Caffeine+Redis两级缓存

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在上一篇文章Redis+Caffeine两级缓存,让访问速度纵享丝滑中,我们介绍了3种整合Caffeine和Redis作为两级缓存使用的方法,虽 ...

  5. MyBatis:二级缓存原理分析

    MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...

  6. J2CACHE 两级缓存框架

    概述 缓存框架我们有ehcache 和 redis 分别是 本地内存缓存和 分布式缓存框架.在实际情况下如果单台机器 使用ehcache 就可以满足需求了,速度快效率高,有些数据如果需要多台机器共享这 ...

  7. springboot中使用自定义两级缓存

    工作中用到了springboot的缓存,使用起来挺方便的,直接引入redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加入@EnableCaching注解,然后在 ...

  8. (4)一起来看下mybatis框架的缓存原理吧

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文只贴我觉得比较重要的源码,其他不重要非关键的就不贴了 我们知道.使用缓存可以更快的获取数据,避免频繁直接查询数据库,节省资源. MyBatis ...

  9. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

随机推荐

  1. Python3实现计算BMI指数,跟我一起来计算你的BMI吧

    废话不多说,直接上程序哈: name=input('Name:') height=input('Height(m):') weight=input('Weight(kg):') BMI=float(f ...

  2. wampserver启动时图标不变绿的解决方法

    有2种可能: 1.你安装wamp的时候安装路径中有中文,把路径全部改为英文. 2.其他软件占用了80端口号,解决方法是在服务中找微软的sql server或者其他服务,关掉服务后重启就行了.

  3. js面向对象之属性

    1.属性的设置和获取,方式有两种:   .和[ ]   .是取自身属性   [ ]可以是变量 var obj={}; obj.name="sonia"; obj['age']=22 ...

  4. SQL——行转列,列转行

    行转列,列转行是我们在开发过程中经常碰到的问题.行转列一般通过CASE WHEN 语句来实现,也可以通过 SQL SERVER 2005 新增的运算符PIVOT来实现.用传统的方法,比较好理解.层次清 ...

  5. 一个WPF小项目小结

    一:缘起 老板有做PC桌面客户端的需求,做的是能耗的计算和评估,要算能耗,就有很多环节,最后对这些环节数据进行一些简单计算.我想要是做的话就用比较熟的wpf,就去聊了下,对方给了1张比较复杂的Exce ...

  6. Html+CSS二周目--->常用概念

    学习css几乎俩周,来总结一下 对于初学者来说,有一些基本的概念是我们应当清楚的.掌握这些概念,可以帮助你更加有效的开发,大大提高开发效率. 1.盒子模型 2.浮动(float) 3.定位(posit ...

  7. 谷歌浏览器web worker出现cannot be accessed from origin 'null'错误

    cannot be accessed from origin 'null'百度翻译是:无法从原点"null"访问 在别的浏览器都可以,而在唯独在谷歌浏览器不行,查找了一些资料原因大 ...

  8. JQ单双引号转义

    var temp = "${row.address_province}"; alert(temp);——————即变量temp alert("\'"+temp+ ...

  9. ztree树形图自定义图标在jeecg框架中不显示

    有时候工作遇到问题,就会硬着头皮去解决,今天给大家说一个ztree树形图自定义图标在jeecg框架中不显示的解决方法 对于这个问题,官方观法说法是在节点元素中加入icon的字段,然后后跟图标的url, ...

  10. 如何领域驱动设计?-实践感悟&总结分享

    主要是在开发过程中,个人对于领域驱动设计的实践感悟和总结:也是对新进开发人员的培训资料:希望对关注DDD的童鞋有所帮助. 概述 领域驱动不是纯粹的技术问题,领域建模(建立数据表只是一部分)是领域专家( ...