Mybatis的缓存机制理解

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/87865507

概述

针对查询操作,mybatis支持通过缓存的方式来减少SQL的调用,提高查询性能。在缓存级别方面分为一级缓存和二级缓存,

  1. 一级缓存的粒度较小,是与某个SqlSession绑定的,只对该SqlSession的相关查询操作进行缓存,不同SqlSession实例之间相互不影响,缓存为使用本地内存实现;
  2. 二级缓存是一种全局缓存,是由所有SqlSession实例所共享的,即不同SqlSession实例查询时产生的缓存,对其他SqlSession实例可见。

一级缓存

  • mybatis的一级缓存支持两种缓存级别,分别是SESSION和STATEMENT,默认的一级缓存级别为SESSION。
  • mybatis的一级缓存是默认开启的。
  • 一级缓存的使用示意图如下:(图片引用自:mybatis一级缓存二级缓存
    在这里插入图片描述

SESSION级别

  • 对该SqlSession实例发起的查询操作进行缓存,即由同一SqlSession实例发起的多次相同(SQL和SQL的参数值都相同)的查询操作,第一次是查询数据库,后续则查询缓存;但是如果另外一个SqlSession实例进行相同的查询操作,则需要进行数据库查询。
  • 针对更新操作,如果是该SqlSession自身进行了更新操作,则该SqlSession对应的一级缓存会被清空,但是如果是其他SqlSession实例进行了更新操作,则此更新操作对该SqlSession不可见,所以该SqlSession的缓存数据是过期失效数据,所以SqlSession实例的生命周期不能过长,否则可能出现数据不一致现象。

STATEMENT级别

  • 该级别是指缓存只针对当前执行的查询语句有效,故每次语句执行完之后都会清空缓存,其实是相当于没有缓存,即该sqlSession实例下次调用相同的SQL语句和相同参数值时,由于上一次语句执行后,缓存被清空了,故需要继续查询数据库。具体可以看源码的query实现:

    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) {
            // 从缓存获取指定SQL的结果,而不用去数据库查询
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 去数据库查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
    
        // 由于SqlSession不是线程安全的,故任何时候只存在一个线程操作,故queryStack总是0的
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
    
          // 如果是STATEMENT,则每次执行完查询都清空缓存,故其实是没有缓存
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
    }
    

配置方法

  • mybatis的一级缓存是内部实现的一个特性,用户不能配置,默认情况下为开启的。同时内部也是使用一个基于HashMap实现的本地内存来实现,故在配置方面只能配置缓存级别为STATEMENT来关闭一级缓存。配置主要是在全局配置mybatisConfig.xml中配置,如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <setting name="localCacheScope" value="STATEMENT"/>
            
            ...
            
        </settings>
        
        ...
    
    </configuration>
    

二级缓存

  • mybatis默认没有开启二级缓存,二级缓存支持在配置中自定义底层所用的缓存实现,包括使用本地内存和分布式缓存。

  • 二级缓存是基于namespace的,即作用域为mapper,故需要在每个mapper中配置自身所使用的二级缓存实现以及缓存策略。同时由于二级缓存是基于namespace的,所以不同namespace之间的相互不影响的,如一个namespace使用的本地内存,另外一个namespace使用的是分布式缓存,则如果不同namespace对同一张数据表的数据进行了操作,则可能会存在数据不一致问题。

  • 如果二级缓存使用本地内存的话,则由于开启二级缓存之后,需要在本地内存缓存大量的数据,即对所有SqlSession实例的查询进行缓存,故可能造成内存资源的开销较大。

  • 二级缓存的使用示意图如下:(图片引用自:mybatis一级缓存二级缓存
    在这里插入图片描述

配置方法

  • 二级缓存的配置分为三步:
    1. 首先在mybatisConfig.xml文件中配置全局开关的:

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <settings>
              <setting name="cacheEnabled" value="true" />
              
              ...
              
          </settings>
          
          ...
      
      </configuration>
      
    2. 然后需要在各个mapper对应的配置文件mapper.xml中配置cache标签,可以指定该mapper使用的二级缓存的底层实现和相关缓存配置等。cache标签也可以是空标签,则使用默认的基于本地内存的二级缓存实现。

      <mapper namespace="dao.userdao">
         
          <!-- Cache 配置 -->
          <!-- <cache /> -->
          <!-- <cache
              eviction="FIFO"
              flushInterval="60000"
              size="512"
              readOnly="true" /> -->
          <cache type="org.mybatis.caches.ehcache.EhcacheCache" />
      </mapper>
      
    3. 这步是可选的,即在mapper内部的每个select可以通过useCache开关来控制当前查询select是否使用二级缓存:默认为true。

      <select id="selectBlog"
              resultMap="BaseResultMap" 
              parameterType="java.lang.Long"
              useCache="false">
          ...
          
      </select>
      

总结

  • 由以上分析可知,虽然一级和二级缓存的使用可以减少数据库查询操作,但是都存在造成数据不一致的情况存在:对于一级缓存由于不同sqlSession实例之间相互隔离,则可能出现其中一个更新了数据库数据,但是另外一个由于使用了自身内部的缓存,故读取到失效的旧数据;对于二级缓存,由所有sqlSession实例共享,基于namespace隔离,故如果不同namespace定义了同时操作一个表的SQL语句,则会造成不同namespace之间的缓存不一致问题。所以如果对于mybatis的内部运作机制不理解,可能会由于这些造成数据不一致的情况存在,则可能会导致莫名其妙的问题。
  • 针对以上这些问题,建议统一使用额外的缓存实现,即在应用代码中自定义缓存实现,关闭mybatis的一级和二级缓存,只使用mybatis基于SQL来进行数据库操作。

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/87865507
今日推荐