MyBatis缓存分析

MyBatis缓存分析

艾瑞斯胡 44 2022-07-22

一、MyBatis中有几级缓存?

有两级缓存,分别是一级缓存和二级缓存

二、什么是一级缓存?什么是二级缓存?

一级缓存

img

一级缓存失效情况

  • sqlsession变了,缓存失效 (不同的session进行查询,缓存失效)
  • sqlsession不变,查询条件不同、中间发生了增删改操作、手动清除缓存三种情况下,一级缓存失效

一级缓存也叫本地缓存,是sqlsession级别的缓存,默认是打开的且不能被关闭。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的

使用SqlSession进行第一次查询时,MyBatis会根据执行的方法和参数,通过一定的算法生成缓存的键值,然后将键值和查询后的结果保存在Map对象中

如果第二次查询,执行的是相同的方法和参数,那么MyBatis就会直接从缓存中这个map中读取。

  @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++;
      // 如果resultHandler不存在, 将从缓存中获取结果集
      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();
      // 如果缓存是STATEMENT级别的,清楚缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

二级缓存

img

二级缓存是 SqlSessionFactory级别的缓存,是与 namespace 进行绑定的

MyBatis的二级缓存实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,
通过Cache接口实现类不同的组合,对Cache的可控性也更强。

MyBatis在多表查询时,极大可能会出现脏数据,这导致安全使用二级缓存的条件比较苛刻,在分布式环境下,建议使用 Redis。

  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);
      // 根据事务对象和execType对象创建对应的执行器对象Executor
      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();
    }
  }



  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);
    }
    // 如果开启了二级缓存, 那么使用wrapper模式包装已经创建的执行器
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }


  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 这里是从MappedStatement中获取的缓存,所以是二级缓存
    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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

三、如何启用一级缓存和二级缓存

1)启用一级缓存

一级缓存默认是开启的

2)启用二级缓存

两个步骤

S1:在mybatis.config.xml文件中的 <settings> 元素中配置 cacheEnabled 参数,默认值:true,设置为false,表示关闭二级缓存

<configuration>
    <properties>
    	......
    </properties>
    
	<settings>
        <!--是否开启二级缓存, 默认是开启的-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
</configuration>

S2:在xxxMapper.xml中添加 <cache />

<cache />
为指定的语句开启或关闭 二级缓存

在xxxMapper.xml文件中的 <select>、<insert>、<update>、<delete>语句中通过 useCacheflushCache设置


# 缓存 # ORM # MyBatis