zoukankan      html  css  js  c++  java
  • mybaits源码分析--缓存模块(六)

    一、缓存模块

    MyBatis作为一个强大的持久层框架,缓存是其必不可少的功能之一,Mybatis中的缓存分为一级缓存和二级缓存。但本质上是一样的,都是使用Cache接口实现的。缓存位于 org.apache.ibatis.cache包下。

    通过结构能够发现Cache其实使用到了装饰器模式来实现缓存的处理。先来看看Cache中的基础类的API;Cache接口的实现类很多,但是大部分都是装饰器,只有PerpetualCache提供了Cache接口的基本实现。

    1.1 Cache接口

    Cache接口是缓存模块中最核心的接口,它定义了所有缓存的基本行为,Cache接口的定义如下:

    public interface Cache {
    
      /**
       * 缓存对象的 ID
       * @return The identifier of this cache
       */
      String getId();
    
      /**
       * 向缓存中添加数据,一般情况下 key是CacheKey  value是查询结果
       * @param key Can be any object but usually it is a {@link CacheKey}
       * @param value The result of a select.
       */
      void putObject(Object key, Object value);
    
      /**
       * 根据指定的key,在缓存中查找对应的结果对象
       * @param key The key
       * @return The object stored in the cache.
       */
      Object getObject(Object key);
    
      /**
       * As of 3.3.0 this method is only called during a rollback
       * for any previous value that was missing in the cache.
       * This lets any blocking cache to release the lock that
       * may have previously put on the key.
       * A blocking cache puts a lock when a value is null
       * and releases it when the value is back again.
       * This way other threads will wait for the value to be
       * available instead of hitting the database.
       *   删除key对应的缓存数据
       *
       * @param key The key
       * @return Not used
       */
      Object removeObject(Object key);
    
      /**
       * Clears this cache instance.
       * 清空缓存
       */
      void clear();
    
      /**
       * Optional. This method is not called by the core.
       * 缓存的个数。
       * @return The number of elements stored in the cache (not its capacity).
       */
      int getSize();
    
      /**
       * Optional. As of 3.2.6 this method is no longer called by the core.
       * <p>
       * Any locking needed by the cache must be provided internally by the cache provider.
       *  获取读写锁
       * @return A ReadWriteLock
       */
      default ReadWriteLock getReadWriteLock() {
        return null;
      }
    
    }

    1.2 PerpetualCache

    PerpetualCache在缓存模块中扮演了ConcreteComponent的角色,其实现比较简单,底层使用HashMap记录缓存项,具体的实现如下

    /**
     * 在装饰器模式用 用来被装饰的对象
     * 缓存中的  基本缓存处理的实现
     * 其实就是一个 HashMap 的基本操作
     * @author Clinton Begin
     */
    public class PerpetualCache implements Cache {
    
      private final String id; // Cache 对象的唯一标识
    
      // 用于记录缓存的Map对象
      private final Map<Object, Object> cache = new HashMap<>();
    
      public PerpetualCache(String id) {
        this.id = id;
      }
    
      @Override
      public String getId() {
        return id;
      }
    
      @Override
      public int getSize() {
        return cache.size();
      }
    
      @Override
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
    
      @Override
      public Object getObject(Object key) {
        return cache.get(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
    
      @Override
      public void clear() {
        cache.clear();
      }
    
      @Override
      public boolean equals(Object o) {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        if (this == o) {
          return true;
        }
        if (!(o instanceof Cache)) {
          return false;
        }
    
        Cache otherCache = (Cache) o;
        // 只关心ID
        return getId().equals(otherCache.getId());
      }
    
      @Override
      public int hashCode() {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        // 只关心ID
        return getId().hashCode();
      }
    
    }

    然后可以来看看cache.decorators包下提供的装饰器。他们都实现了Cache接口。这些装饰器都在PerpetualCache的基础上提供了一些额外的功能,通过多个组合实现一些特殊的需求。

    1.3 BlockingCache

    这是一个阻塞同步的缓存,它保证只有一个线程到缓存中查找指定的key对应的数据

    /**
     * Simple blocking decorator
     *   阻塞版的缓存 装饰器
     * Simple and inefficient version of EhCache's BlockingCache decorator.
     * It sets a lock over a cache key when the element is not found in cache.
     * This way, other threads will wait until this element is filled instead of hitting the database.
     *
     * @author Eduardo Macarron
     *
     */
    public class BlockingCache implements Cache {
    
      private long timeout; // 阻塞超时时长
      private final Cache delegate; // 被装饰的底层 Cache 对象
      // 每个key 都有对象的 ReentrantLock 对象
      private final ConcurrentHashMap<Object, ReentrantLock> locks;
    
      public BlockingCache(Cache delegate) {
        // 被装饰的 Cache 对象
        this.delegate = delegate;
        this.locks = new ConcurrentHashMap<>();
      }
    
      @Override
      public String getId() {
        return delegate.getId();
      }
    
      @Override
      public int getSize() {
        return delegate.getSize();
      }
    
      @Override
      public void putObject(Object key, Object value) {
        try {
          // 执行 被装饰的 Cache 中的方法
          delegate.putObject(key, value);
        } finally {
          // 释放锁
          releaseLock(key);
        }
      }
    
      @Override
      public Object getObject(Object key) {
        acquireLock(key); // 获取锁
        Object value = delegate.getObject(key); // 获取缓存数据
        if (value != null) { // 有数据就释放掉锁,否则继续持有锁
          releaseLock(key);
        }
        return value;
      }
    
      @Override
      public Object removeObject(Object key) {
        // despite of its name, this method is called only to release locks
        releaseLock(key);
        return null;
      }
    
      @Override
      public void clear() {
        delegate.clear();
      }
    
      private ReentrantLock getLockForKey(Object key) {
        return locks.computeIfAbsent(key, k -> new ReentrantLock());
      }
    
      private void acquireLock(Object key) {
        Lock lock = getLockForKey(key);
        if (timeout > 0) {
          try {
            boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
            if (!acquired) {
              throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
            }
          } catch (InterruptedException e) {
            throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
          }
        } else {
          lock.lock();
        }
      }
    
      private void releaseLock(Object key) {
        ReentrantLock lock = locks.get(key);
        if (lock.isHeldByCurrentThread()) {
          lock.unlock();
        }
      }
    
      public long getTimeout() {
        return timeout;
      }
    
      public void setTimeout(long timeout) {
        this.timeout = timeout;
      }
    }

    通过源码我们能够发现,BlockingCache本质上就是在操作缓存数据的前后通过ReentrantLock对象来实现了加锁和解锁操作。

    缓存实现类 缓存实现类 作用 装饰条件

    基本缓存



    缓存基本实现类

    默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类
    LruCache

    LRU策略的缓存

    当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use)

    eviction="LRU"(默认)
    FifoCache

    FIFO策略的缓存

    当缓存到达上限时候,删除最先入队的缓存

    eviction="FIFO"
    SoftCacheWeakCache

    带清理策略的缓存

    通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference

    eviction="SOFT"eviction="WEAK"
    LoggingCache

    带日志功能的缓存

    比如:输出缓存命中率 基本
    SynchronizedCache

    同步缓存

    基于synchronized关键字实现,解决并发问题

    基本
    BlockingCache

    阻塞缓存

    通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现

    blocking=true
    SerializedCache

    支持序列化的缓存

    将对象序列化以后存到缓存中,取出时反序列化

    readOnly=false(默认)
    ScheduledCache

    定时调度的缓存

    在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认
    是一小时),如果是则清空缓存--即每隔一段时间清空一次缓存

    flushInterval不为空
    TransactionalCache

    事务缓存

    在二级缓存中使用,可一次存入多个缓存,移除多个缓存

    在TransactionalCacheManager中用Map维护对应关系

    1.4 缓存的应用

    1.4.1 缓存对应的初始化

    在之前写的代码中断个点看下可能直接点,在断点前说明下要求,如要开启缓存要在配置文件开启一级和二级缓存

    然后呢在mapper.XML文件加入<cache/>标签就可以了

     下面来断点看下

     

     通过上面截图可以很清楚的看到这是一个装饰器过程,接下来看下在Configuration初始化的时候怎么给我们的各种Cache实现注册对应的别名

     在解析settings标签的时候,设置的默认值有如下;因为前面源码跟了好多次,这里面我直接进到解析这一段代码了

     public Configuration parse() {
        //检查是否已经解析过了
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // XPathParser,dom 和 SAX 都有用到 >>
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          // 对于全局配置文件各种标签的解析
          propertiesElement(root.evalNode("properties"));
          // 解析 settings 标签
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          // 读取文件
          loadCustomVfs(settings);
          // 日志设置
          loadCustomLogImpl(settings);
          // 类型别名
          typeAliasesElement(root.evalNode("typeAliases"));
          // 插件
          pluginElement(root.evalNode("plugins"));
          // 用于创建对象
          objectFactoryElement(root.evalNode("objectFactory"));
          // 用于对对象进行加工
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          // 反射工具箱
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          // settings 子标签赋值,默认值就是在这里提供的 >>
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          // 创建了数据源 >>
          environmentsElement(root.evalNode("environments"));
          //解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          // 解析引用的Mapper映射器
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

    在上面的全局配置文件中在settingsElement(settings);的赋值中会做一些默认的处理,点进去看下

     通过上面发现cacheEnabled默认为true,localCacheScope默认为 SESSION,在初始化过程中关键的还是映射文件的解析,点击mapperElement(root.evalNode("mappers"));进去看下

     private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应
            // package    包
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                // resource    相对路径
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                // 解析 Mapper.xml,总体上做了两件事情 >>
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                // url    绝对路径
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                // class     单个接口
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }

    直接进入他的关键代码mapperParser.parse();,

     public void parse() {
        // 总体上做了两件事情,对于语句的注册和接口的注册
        if (!configuration.isResourceLoaded(resource)) {
          // 1、具体增删改查标签的解析。
          // 一个标签一个MappedStatement。 >>
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
          // 一个namespace 一个 MapperProxyFactory >>
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }

    上面是映射文件的解析操作,可以看他进了标签的解析,进去看下

      private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          // 添加缓存对象
          cacheRefElement(context.evalNode("cache-ref"));
          // 解析 cache 属性,添加缓存对象
          cacheElement(context.evalNode("cache"));
          // 创建 ParameterMapping 对象
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          // 创建 List<ResultMapping>
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          // 解析可以复用的SQL
          sqlElement(context.evalNodes("/mapper/sql"));
          // 解析增删改查标签,得到 MappedStatement >>
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }

    看到这里好像找到了想找的东西,可以看到上面代码我标的两个地方的标签解析,跟进去看下

     private void cacheElement(XNode context) {
        // 只有 cache 标签不为空才解析
        if (context != null) {
          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);
        }
      }

    会发现上面开始解析相关的属性信息了,并在最后一步进行了保存,继续跟进去看下

     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);
        currentCache = cache;
        return cache;
      }

    然后可以发现 如果存储 cache 标签,那么对应的 Cache对象会被保存在 currentCache 属性中。

     

     进而在 Cache 对象 保存在了 MapperStatement 对象的 cache 属性中。这就是cache节点创建的整个过程。

    1.4.2 一级缓存

    一级缓存也叫本地缓存(Local Cache),MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis的一级缓存是默认开启的,不需要任何的配置(如果要关闭,localCacheScope设置为STATEMENT)。在BaseExecutor对象的query方法中有关闭一级缓存的逻辑

     

     从上面的效果可以很清楚的感受到在一个会话内,第二次查询是直接走缓存的,在不同会话内缓存是不起效的。下面会了解缓存做了啥跟进代码看下。入口从上面演示就可以猜到是从

    SqlSession sqlSession = factory.openSession();进入的
      @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);
          // 根据事务工厂和默认的执行器类型,创建执行器 >>执行SQL语句操作
          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) {//针对Statement做缓存
          executor = new ReuseExecutor(this, transaction);
        } else {
          // 默认 SimpleExecutor,每一次只是SQL操作都创建一个新的Statement对象
          executor = new SimpleExecutor(this, transaction);
        }
        // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        // 植入插件的逻辑,至此,四大对象已经全部拦截完毕;这里面是一个拦截器链
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    从上面代码可以知道如果 cacheEnabled 为 true 就会通过 CachingExecutor 来装饰executor 对象,然后就是在执行SQL操作的时候会涉及到缓存的具体使用。这个就分为一级缓存和二级缓存,通过这个跟踪会发现在创建会话时会创建执行器,而执行器里面跟缓存有关系的是二级缓存,跟我想找的一级缓存没什么关系;那么一级缓存在哪呢,这时候我想一级缓存是跟会话有关,那么他的位置一定在会话内的这段代码里,那我就找下一段代码

     // 4.通过SqlSession中提供的 API方法来操作数据库
            List<User1> list = sqlSession.selectList("com.ghy.mapper.UserMapper.selectUserList");

    进入selectList看下

      @Override
      public <E> List<E> selectList(String statement) {
        return this.selectList(statement, null);
      }
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
          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();
        }
      }

    在上面代码中可以看到一个查询操作,那肯定是要进去看下他在查询前有没有缓存判断,如果没有说明selectList代码是不走缓存的;

     在上面代码中发现了一些跟缓存相关的操作CacheKey

     @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
    
        cacheKey.update(ms.getId());
        cacheKey.update(rowBounds.getOffset()); // 0
        cacheKey.update(rowBounds.getLimit()); // 2147483647 = 2^31-1
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value); // development
          }
        }
        if (configuration.getEnvironment() != null) {
          // issue #176
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }

    发现上面是一个缓存创建的逻辑,这个东西debugger看一下其实就明白了;其实这写了一堆就是生成一个东西,生成一个缓存的KEY,而且这个KEY是跟我们写的SQL有关;明白了这个key的作用后回退一步跟进query看他拿这个key去做了什么

     @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 异常体系之 ErrorContext
        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()) {
          // flushCache="true"时,即使是查询,也清空一级缓存
          clearLocalCache();
        }
        List<E> list;
        try {
          // 防止递归查询重复处理缓存
          queryStack++;
          // 查询一级缓存
          // ResultHandler 和 ResultSetHandler的区别
          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;
      }

    从上面就找到了查找一级缓存的位置了,如果list判断是空说明缓存没数据他会走queryFromDatabase去查询并且把数据缓存起来

     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 {
          // 三种 Executor 的区别,看doUpdate
          // 默认Simple
          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;
      }

    1.4.3 二级缓存

    二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。二级缓存的设置,首先是settings中的cacheEnabled要设置为true,当然默认的就是为true,这个步骤决定了在创建Executor对象的时候是否通过CachingExecutor来装饰。前面源码中也有说明过;要想看二级缓存效果,</cache>标签要打开

     然后把一级缓存配置关闭了,其实由于一级缓存的作用域太小,在实际生产中用的也比较少

     从上面可以发现第二次查询就没走数据库查询,说明二级缓存生效了。接下来看下二级缓存源码,其实在上面已经写出来了,入口是factory.openSession();

      @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);
          // 根据事务工厂和默认的执行器类型,创建执行器 >>执行SQL语句操作
          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) {//针对Statement做缓存
          executor = new ReuseExecutor(this, transaction);
        } else {
          // 默认 SimpleExecutor,每一次只是SQL操作都创建一个新的Statement对象
          executor = new SimpleExecutor(this, transaction);
        }
        // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        // 植入插件的逻辑,至此,四大对象已经全部拦截完毕;这里面是一个拦截器链
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    从这里可以看到如果判断成立,那么会对executor做一个装饰;后面做查询操作时就要从sqlSession.selectList("com.ghy.mapper.UserMapper.selectUserList");跟踪起了

      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰
          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();
        }
      }

    一样,进入query方法看他是怎么执行的

     这里要进的就是CachingExecutor里面了,这里面是二级缓存的东西

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取SQL
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 创建CacheKey:什么样的SQL是同一条SQL? >>
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    这里面创建createCacheKey的过程和一级缓存一样,这里就不想再写一次了;
     @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        Cache cache = ms.getCache();
        // cache 对象是在哪里创建的?  XMLMapperBuilder类 xmlconfigurationElement()
        // 由 <cache> 标签决定
        if (cache != null) {
          // flushCache="true" 清空一级二级缓存 >>
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, boundSql);
            // 获取二级缓存
            // 缓存通过 TransactionalCacheManager、TransactionalCache 管理
            @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;
          }
        }
        // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }

    这就是二级缓存过程;

    cache属性详解:

    属性 含义 取值
    type

    缓存实现类

    需要实现Cache接口,默认是PerpetualCache,可以使用第三方缓存
    size

    最多缓存对象个数

    默认1024
    eviction

    回收策略(缓存淘汰算法)

    LRU – 最近最少使用的:移除最长时间不被使用的对象(默认)。FIFO
    – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除
    基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地
    移除基于垃圾收集器状态和弱引用规则的对象。

    flushInterval

    定时自动清空缓存间隔

    自动刷新时间,单位 ms,未配置时只有调用时刷新
    readOnly

    是否只读

    true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些
    对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返
    回缓存对象的拷贝(通过序列化),不会共享。这会慢一些,但是安
    全,因此默认是 false。改为false可读写时,对象必须支持序列化。

    blocking

    启用阻塞缓存

    通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现


    1.4.4 第三方缓存

    在实际开发的时候我们一般也很少使用MyBatis自带的二级缓存,这时我们会使用第三方的缓存工具Ehcache获取Redis来实现 https://github.com/mybatis/redis-cache

    添加依赖

            <dependency>
                <groupId>org.mybatis.caches</groupId>
                <artifactId>mybatis-redis</artifactId>
                <version>1.0.0-beta2</version>
            </dependency>

    然后加上Cache标签的配置

    <cache type="org.mybatis.caches.redis.RedisCache"
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

    然后添加redis的属性文件

     这样缓存就存入redis中了,至于怎么读到redis.properites文件的,这个可以从源码中找下

     从上面看到在构造方法中会做一些初始的操作,其中的JedisPool是操作连接去操作redis的;

        public RedisConfig parseConfiguration() {
            return parseConfiguration(getClass().getClassLoader());
        }

     从源码中可以发现他已经做好了redis连接配置文件的默认命名了;

    这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦
  • 相关阅读:
    poj3041(最小顶点覆盖)
    High-speed Charting Control--MFC绘制图表(折线图、饼图、柱形图)控件
    hdu 3183 A Magic Lamp(RMQ)
    Android studio 中创建AIDL Service
    cocos2d-x 3.0正式版 cmd创建project以及一键创建project
    【Machine Learning】决策树案例:基于python的商品购买能力预测系统
    【Machine Learning】机器学习及其基础概念简介
    【Machine Learning】Python开发工具:Anaconda+Sublime
    【HanLP】HanLP中文自然语言处理工具实例演练
    【HanLP】资料链接汇总
  • 原文地址:https://www.cnblogs.com/xing1/p/15208024.html
Copyright © 2011-2022 走看看