• Mybatis的缓存


    Mybatis的缓存

    mybatis是一个查询数据库的封装框架,主要是封装提供灵活的增删改sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybatis有使用缓存,这里讲一下mybatis的缓存相关源码。

    缓存

    在计算机里面,任何信息都有源头,缓存一般指源头信息读取后,放在内存或者其他读取较快的地方,下次读取相同信息不去源头查询而是直接从内存(或者能快速存取的硬件)读取。这样可以减少硬件使用,提高读取速度。

    mybatis也是这样,查询数据库的数据之后,mybatis可以把查询结果缓存到内存,下次查询如果查询语句相同,并且查询相关的表的数据没被修改过,就可以直接返回缓存中的结果,而不用去查询数据库的语句,有效节省了时间。

    关于mybatis中一级和二级缓存命名

    缓存概念较早用于CPU读取数据,有一级和二级缓存,读取顺序是先一级缓存,再二级缓存。

    按照这个概念,通过源码了解mybatis的Mapper中的缓存是一级缓存,SqlSession的中缓存是二级缓存。看到一些介绍mybatis缓存的相关文章命名反过来的,称SqlSession中的缓存称为一级缓存,对此有疑惑...

    简单看一下mybatis缓存相关源码

    Mapper中的缓存(一级)

    mapper中的缓存,默认配置是开启,但需要在映射文件mapper.xml中添加<cache/>标签

    <mapper namespace="userMapper">
    	<cache/><!-- 添加cache标签表示此mapper使用缓存 -->
    </mapper>
    

    配置false可以关闭mapper中的缓存

    mybatis:
      configuration:
         cache-enabled: false #默认值为true,表示开启
    

    mapper缓存的解析

    org.apache.ibatis.builder.xml.XMLMapperBuilder

      private void configurationElement(XNode context) {
        try {
          //...
          cacheElement(context.evalNode("cache")); //解析mapper.xml中的cache标签
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }
    
      private void cacheElement(XNode context) {
        if (context != null) { // if hava cache tag 如果有cache标签才执行下面的逻辑
          String type = context.getStringAttribute("type", "PERPETUAL");
          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
          String eviction = context.getStringAttribute("eviction", "LRU");
          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
          Long flushInterval = context.getLongAttribute("flushInterval");
          Integer size = context.getIntAttribute("size");
          boolean readWrite = !context.getBooleanAttribute("readOnly", false);
          boolean blocking = context.getBooleanAttribute("blocking", false);
          Properties props = context.getChildrenAsProperties();
          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);//建立mapper缓存
        }
      }
    

    org.apache.ibatis.builder.MapperBuilderAssistant.useNewCache():

      public Cache useNewCache(Class<? extends Cache> typeClass,
          Class<? extends Cache> evictionClass,
          Long flushInterval,
          Integer size,
          boolean readWrite,
          boolean blocking,
          Properties props) {
        Cache cache = new CacheBuilder(currentNamespace)
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
        configuration.addCache(cache);//mapper缓存赋值,如果cache标签为空,不会执行此方法,currentCache为空
        currentCache = cache; 
        return cache;
      }
    

    在映射文件mapper中如果没有cache标签,解析时不会执行上面的useNewCache方法,cache为null,就不会使用mapper缓存(相当于失效)。

    查询使用mapper缓存逻辑

    org.apache.ibatis.executor.CachingExecutor :

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        Cache cache = ms.getCache(); //获取mapper缓存
        if (cache != null) {//如果mapper缓存对象不为空 尝试在mapper缓存中获取(没有cache标签此对象就是空)
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            @SuppressWarnings("unchecked")
            List<E> list = (List<E>) tcm.getObject(cache, key); //从mapper缓存中获取数据
            if (list == null) {
              list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //如果为空,使用delegate查询(BaseExecutor)
              tcm.putObject(cache, key, list); // 查询结果保存到mapper缓存
            }
            return list;
          }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    

    上面代码中ms对象(MappedStatement )是在系统启动时创建的对象,cache也是,不与SqlSession绑定,所以SqlSession不同,mapper缓存依然可以使用,这区别于SqlSession中的缓存。

    SqlSession中的缓存(二级)

    通过查看源码可知,SqlSsession中是有缓存的,所以每次(新请求)会话SqlSession不同,缓存是空的;相同的SqlSession中的缓存才有效。

    mybatis默认Sqlsession:org.apache.ibatis.session.defaults.DefaultSqlSession

    构造方法中传入executor(查询执行对象)

      public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
      }
    

    executor中携带二级缓存成员变量:

      protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<>();
        this.localCache = new PerpetualCache("LocalCache"); //默认SqlSession中的缓存
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
      }
    

    查询使用SqlSession缓存逻辑

    org.apache.ibatis.executor.BaseExecutor.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());
        
        List<E> list;
        try {
          queryStack++;
          	//localCache SqlSession中的缓存
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            //先从SqlSession中的缓存中获取,key是通过sql语句生成
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            // 如果缓存中没有 才从数据库查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        return list;
      }
    
      //从数据库读取数据
      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);//将SqlSession中的缓存清除
        }
        localCache.putObject(key, list);//返回查询结果之前,放入SqlSession中的缓存 刷新
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    

    二级缓存和一级缓存不用想,数据库的数据被修改时要清空缓存,不然数据有误;至于怎么清空,是另一套逻辑,mapper中的cache标签可以配置一些参数,比如缓存定期清空。

    一级二级缓存先后顺序

    从概念上来将,先读取的就是一级缓存,后读取是二级缓存,那么Mapper中的缓存是一级缓存。

    通过newExecutor源码可以知道这里使用了类似装饰者模式对executor进行了包装,在创建Executor的时候,逻辑如下

    org.apache.ibatis.session.Configuration.newExecutor()

      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);//创建CachingExecutor
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    
      //CachingExecutor构造
      public CachingExecutor(Executor delegate) {
        this.delegate = delegate; //进行一层包装 delegate = BaseExecutor
        delegate.setExecutorWrapper(this);
      }
    

    开启缓存,查询逻辑从SqlSession.selectList开始,先调用CachingExecutor对象

    org.apache.ibatis.session.defaults.DefaultSqlSession.selectList()

      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
          //...
          //executor = CachingExecutor
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      }
    

    1 CachingExecutor.query 中尝试读取mapper中的缓存(一级)

    org.apache.ibatis.executor.CachingExecutor.query()

    //1 Mapper cache
    Cache cache = ms.getCache();
    if (cache != null) {
          //...
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
              list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // delegate.query is BaseExecutor.query
            }
            return list;
          }
        }
    

    2 BaseExecutor.query 中尝试读取SqlSession中的缓存(二级)

    org.apache.ibatis.executor.BaseExecutor.query()

    //2 SqlSession cache
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
        //3 DabaBase
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    

    3 最后一级二级缓存都没有,就查询数据库

  • 相关阅读:
    几何画板绘制三棱锥的教程
    MathType给公式底部加箭头的教程
    几何画板有哪些快捷键可以用
    公式编辑器调整公式边框粗细的教程
    wdcp安装
    搭建git for windows服务器(100%可以成功)
    百度echarts
    简单的js菜单
    真正的让iframe自适应高度 兼容多种浏览器随着窗口大小改变
    Hadoop学习笔记(一)从官网下载安装包
  • 原文地址:https://www.cnblogs.com/Narule/p/14284868.html
Copyright © 2020-2023  润新知