『手撕 Mybatis 源码』11 - 二级缓存

二级缓存

概述

  1. 启用二级缓存需要进行三步配置
  • 开启映射器配置文件中的缓存配置
  <settings>
    <!--cacheEnabled值默认就为true-->
    <setting name="cacheEnabled" value="true"/>
  </settings>
  • 在需要使用二级缓存的 Mapper 配置文件中配置标签
  <!--type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
      eviction: 定义回收的策略,常见的有FIFO,LRU。
      flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
      size: 最多缓存对象的个数。
      readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
      blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
      -->
  <cache></cache>
  • 在具体 CURD 标签上配置 useCache=true
  <!--useCache默认值也是true-->
  <select id="findByCondition" resultType="com.itheima.pojo.User" useCache="true">
    SELECT id, name FROM  user WHERE id = #{id}
  </select>
  1. 新增二级缓存测试,测试的 case 先是创建一个会话,执行一次查询,然后其中一个会话进行一次 commit()(否则没办法生效二级缓存),然后再使用另外一个会话查询一次
public class CacheTest {
  /**
   * 测试二级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession() 执行逻辑是什么?
    // 3. (1)创建事务对象 (2)创建了执行器对象 cachingExecutor (3)创建了 DefaultSqlSession 对象
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 发起第一次查询,查询ID为1的用户
    User user1 = sqlSession1.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 必须要调用 sqlSession 的 commit 方法或者 close() 方法,才能让二级缓存生效
    sqlSession1.commit();

    // 第二次查询
    User user2 = sqlSession2.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 返回是 false,因为存储的是数据本身,所以两个不是同一个地址
    System.out.println(user1==user2);
    System.out.println(user1);
    System.out.println(user2);

    sqlSession1.close();
  }
}

标签 <cache> 解析

  1. 问题
  • cache 标签如何被解析的?
  1. 首先继续回到解析配置文件部分
public class CacheTest {
  /**
   * 测试二级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    ...
  }
}
  1. SqlSessionFactoryBuilder 创建 XMLConfigBuilder 解析配置文件,然后开始分析
public class SqlSessionFactoryBuilder {
  ...
   public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder:用来解析XML配置文件
      // 使用构建者模式(至少4个以上成员变量):好处:降低耦合、分离复杂对象的创建
      // 1. 创建 XPathParser 解析器对象,根据 inputStream 解析成了 document 对象 
      // 2. 创建全局配置对象 Configuration 对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

      // parser.parse():使用XPATH解析XML配置文件,将配置文件封装到Configuration对象
      // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
      // 3. parse():配置文件就解析完成了
      return build(parser.parse());

    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
}
  1. XMLConfigBuilder 开始从根节点 /configuration 开始解析,因为关注的是二级缓存,我们从解析 /mappers 开始
public class XMLConfigBuilder extends BaseBuilder {
  
  private final XPathParser parser;
  ...
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;

    // parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
    // 1. 从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
  private void parseConfiguration(XNode root) {
    try {
      ...
      // 2. 解析 </mappers> 标签 加载映射文件流程主入口
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
}
  1. mapperElement() 在解析 /mapper 节点时,不管是解析的是要通过 /package 节点还是本身 /mapper解析,最终会调用 XMLMapperBuilder 来解析 mapper 映射文件
public class XMLConfigBuilder extends BaseBuilder {

  protected final Configuration configuration;
  ...
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 获取<mappers>标签的子标签
      for (XNode child : parent.getChildren()) {
        // <package>子标签
        if ("package".equals(child.getName())) {
          // 获取mapper接口和mapper映射文件对应的package包名
          String mapperPackage = child.getStringAttribute("name");
          // 1. 将包下所有的 mapper 接口以及它的代理工厂对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
          configuration.addMappers(mapperPackage);
        } else {// <mapper>子标签
          ....
        }
      }
    }
  }
}
  1. configuration.addMappers() 最终就交由 XMLMapperBuilder 来解析 mapper 映射文件
public class XMLMapperBuilder extends BaseBuilder {
  
  protected final Configuration configuration;
  private final XPathParser parser;
  ...
  public void parse() {
    // mapper 映射文件是否已经加载过 resource = "mapper/UserMapper.xml"
    if (!configuration.isResourceLoaded(resource)) {

      // 1. 从映射文件中的<mapper>根标签开始解析,直到完整的解析完毕
      configurationElement(parser.evalNode("/mapper"));
     ...
    }
    ...
  }
}
  1. XMLMapperBuilder 解析 mapper.xml 时,就会解析对应的 <cache> 子标签,这个是开启二级缓存的标志
public class XMLMapperBuilder extends BaseBuilder {
   ...
  private void configurationElement(XNode context) {
    try {
      ...
      // 解析<cache>子标签
      cacheElement(context.evalNode("cache"));
      ...
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
}
  1. 解析 /cache 节点时,首先检查是否已经指定 type 属性,如果有,则创建指定 type 的缓存对象,否则默认的二级缓存,创建的是 PerpetualCache,然后根据二级缓存的所有配置,创建对应的属性对象,通过 builderAssistant.useNewCache() 创建二级缓存
public class XMLMapperBuilder extends BaseBuilder {
  ...
  private final MapperBuilderAssistant builderAssistant;
  protected final TypeAliasRegistry typeAliasRegistry;

  private void cacheElement(XNode context) {
    if (context != null) { 
      // 如果是指定 <cache type="redisCache"> </cache>,那就用配置的,否则用 PERPETUALCache
      // 1. 解析<cache>标签type属性的值,在这可以自定义type的值,比如redisCache,如果没有指定默认就是PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 获取负责过期的eviction对象,默认策略为LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 清空缓存的频率 0代表不清空
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 缓存容器的大小
      Integer size = context.getIntAttribute("size");
      // 是否只读
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 是否阻塞
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 获得 Properties 属性
      Properties props = context.getChildrenAsProperties();
      // 2. 创建二级缓存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
}
  1. MapperBuilderAssistant 就会将所有 /cache 属性组装生成 cache 对象,然后添加到 configuration 对象中(其中内部会获取 cache 对象的 id:com.itheima.mapper.UserMapper 保存到 Map 中),然后把缓存赋值给 currentCache 属性,等会还得使用
public class MapperBuilderAssistant extends BaseBuilder {

  // 当前 cache 对象
  private Cache currentCache;
  ...
  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 1. 生成 cache 对象
    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();
    // 2. 添加到configuration中
    configuration.addCache(cache);
    // 3. 并赋值给 MapperBuilderAssistant 中的 currentCache 属性
    currentCache = cache;
    return cache;
  }
}
  1. 解析完 /cache 节点后,还需要解析 /select|/insert|/update|/delete 节点,因为这里会把刚刚创建的二级缓存也放到 MappedStatement
public class XMLMapperBuilder extends BaseBuilder {
  ...
  /**
   *  解析映射文件
   * @param context 映射文件根节点<mapper>对应的XNode
   */
  private void configurationElement(XNode context) {
    try {
      ...
      // 1. 按顺序解析 <select>\<insert>\<update>\<delete> 子标签
      // 2. 将cache对象封装到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);
    }
  }
}
  1. 下面解析 /select|/insert|/update|/delete 节点前,会分别创建 XMLStatementBuilder 来解析并创建 MappedStatement 对象
public class XMLMapperBuilder extends BaseBuilder {
  protected final Configuration configuration;
  ...
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) { //# 判断是否配置过 databaseId
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 1. 构建 MappedStatement
    buildStatementFromContext(list, null);
  }

  /**
   * 2、专门用来解析MappedStatement
   * @param list
   * @param requiredDatabaseId
   */
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 2. MappedStatement 解析器
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 3. 解析 select 等 4 个标签,创建 MappedStatement 对象
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
}
  1. statementParser.parseStatementNode() 解析节点时,先判断是否标记了 useCache 属性,然后通过构建者助手,创建 MappedStatement 对象
public class XMLStatementBuilder extends BaseBuilder {

  private final MapperBuilderAssistant builderAssistant;
  private final XNode context;
  ...
  /**
   * 解析<select>\<insert>\<update>\<delete>子标签
   */
  public void parseStatementNode() {
    ...
    // 1. 获取 sql 上的属性,默认就是 true
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    ...
    // 2. 通过构建者助手,创建 MappedStatement 对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}
  1. 然后 MapperBuilderAssistant 当时是把二级缓存放到了 currentCache 属性中,为的就是把创建出来的缓存存入 MappedStatement 中,创建 MappedStatement 后,存入 Configuration 对象,最终返回构建好的 DefaultSqlSessionFactory。从上面流程可以看出,二级缓存,其实是配置文件命名空间下共享的
public class MapperBuilderAssistant extends BaseBuilder {

  // 当前cache对象
  private Cache currentCache;
  protected final Configuration configuration;
  ...
  /**
   *  通过构建者助手,创建MappedStatement对象
   * @param id
   * @param sqlSource
   * @param statementType
   * @param sqlCommandType
   * @param fetchSize
   * @param timeout
   * @param parameterMap
   * @param parameterType
   * @param resultMap
   * @param resultType
   * @param resultSetType
   * @param flushCache
   * @param useCache
   * @param resultOrdered
   * @param keyGenerator
   * @param keyProperty
   * @param keyColumn
   * @param databaseId
   * @param lang
   * @param resultSets
   * @return
   */
  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    //利用构建者模式,去创建MappedStatement.Builder,用于创建MappedStatement对象
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
        // 1. 将 cache 对象存入到 MappedStatement中,相同 Mapper 中的 MappedStatement 共用同一个 Cache

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    // 2. 通过 MappedStatement.Builder,构建一个 MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 3. 将 MappedStatement 对象存储到 Configuration 中的 Map 集合中,key 为 statement 的 id,value 为 MappedStatement 对象
    configuration.addMappedStatement(statement);
    return statement;
  }
}
  1. 总结
    在这里插入图片描述

二级缓存执行

  1. 问题
  • 同时开启一级缓存,二级缓存。优先级?
  • 为什么只有执行 sqlSession.commit() 或者 sqlSession.close() 二级缓存才会生效
  1. 继续沿用上一次的测试 case 来研究解决这些问题
public class CacheTest {
  /**
   * 测试二级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3.问题:openSession()执行逻辑是什么?
    // 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    // 发起第一次查询,查询ID为1的用户
    User user1 = sqlSession1.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // **必须要调用sqlSession的commit方法或者close方法,才能让二级缓存生效
    sqlSession1.commit();
    // 第二次查询
    User user2 = sqlSession2.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 返回是 false,因为存储的是数据本身,所以两个不是同一个地址
    System.out.println(user1==user2);
    System.out.println(user1);
    System.out.println(user2);

    sqlSession1.close();
  }
}
  1. 首先第一次执行 selectOne() 继续经过多次 selectList() 方法,生成 MappedStatement 然后委派给 Executor 执行
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 根据传入的 statementId 即 user.findUserById,获取 MappedStatement 对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 1. 调用执行器的查询方法
      // wrapCollection(parameter)是用来装饰集合或者数组参数
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 执行查询时,会先生成缓存键 CacheKey,然后从 MappedStatement 中获取二级缓存,如果 MappedStatement 对应的 sql 语句配置了 flushCache=true,那么就会在执行前刷新缓存。这里的 case 并没有配置,所以继续执行。尝试从二级缓存中获取数据
public class CachingExecutor implements Executor {
  
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  ...
  //第一步
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取绑定的 SQL 语句,比如 "SELECT * FROM user WHERE id = ? "
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 1. 生成缓存Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 2. 获取二级缓存,需要在配置文件中配置 <cache></cache> 标签
    //   <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" flushCache="true">
    Cache cache = ms.getCache();// SynchronizeCache 是包装类,实际调用是 PerpetualCache
    if (cache != null) {
      // 3. 刷新(每次查询前清空)二级缓存 (存在缓存且 flushCache 为true时)
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) { // 默认就是 true    <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" useCache="true">

        ensureNoOutParams(ms, boundSql);// 处理输出参数
        @SuppressWarnings("unchecked")
        // 4. 从二级缓存中查询数据
        List<E> list = (List<E>) tcm.getObject(cache, key);
        ...
        return list;
      }
    }
    // 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  1. 这里实际是由事务缓存管理器 TransactionalCacheManager 来获取缓存数据,它只有一个关键属性 transactionalCaches,其中 key 是二级缓存,而 TransactionalCache 是对应的一个临时事务缓存,一开始时 TransactionalCache 还没创建,所以会先创建,然后从 TransactionalCache 获取数据。后面才明白 TransactionalCache 的作用
/**
 * @author Clinton Begin
 * 事务缓存管理器
 */
public class TransactionalCacheManager {

  // Cache(原生的 PertualCache) 与 TransactionalCache(队员生的包装对象) 的映射关系表
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  ...
  public Object getObject(Cache cache, CacheKey key) {
    // 1. 直接从 TransactionalCache 中获取缓存
    return getTransactionalCache(cache).getObject(key);
  }
  
  private TransactionalCache getTransactionalCache(Cache cache) {
    // 从映射表中获取 TransactionalCache,没有则新建
    return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
  }
}
  1. 实际 TransactionalCache 包装了二级缓存,所以在执行 getObject() 方法时,会先从二级缓存中拿到数据,如果拿不到,则存入一个 entriesMissedInCache 的 Set 集合中,返回返回一个空对象
public class TransactionalCache implements Cache {

  /**
   * 委托的 Cache 对象。
   *
   * 实际上,就是二级缓存 Cache 对象。
   */
  private final Cache delegate;
  
  /**
   *   在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
   */
  private final Set<Object> entriesMissedInCache;
  @Override
  public Object getObject(Object key) {
    // issue #116
    // 1. 查询的时候是直接从 delegate 中去查询的,也就是从真正的缓存对象中查询
    Object object = delegate.getObject(key);
    // 2. 如果不存在,则添加到 entriesMissedInCache 中
    if (object == null) {
      // 3. 缓存未命中,则将 key 存入到 entriesMissedInCache 中
      entriesMissedInCache.add(key);
    }
    // issue #146
    // 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
    if (clearOnCommit) {
      return null;
    } else {
      // 4. 返回 value
      return object;
    }
  }
}
  1. 第一次执行 selectOne() 肯定是没有缓存结果的,所以 CachingExecutor 还是会委派给 SimpleExecutor 执行查询
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取二级缓存,需要在配置文件中配置 <cache></cache> 标签
    //   <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" flushCache="true">
    Cache cache = ms.getCache();// SynchronizeCache 是包装类,实际调用是 PerpetualCache
    if (cache != null) {
       ...
        if (list == null) {
          // 1. 委托给 BaseExecutor 执行
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          ...
        }
        return list;
      }
    }
    // 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  1. SimpleExecutor 执行查询则首先通过一级缓存获取数据,如果没有数据,就从数据查询,查询后将数据存入一级缓存,然后返回数据。从这里得知第一个问题答案,明显是二级缓存最优先
public abstract class BaseExecutor implements Executor {
   
  protected PerpetualCache localCache;
  ...
  @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.");
    }
    // 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清空缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      // 查询堆栈 + 1
      queryStack++;
      // 1. 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 已有缓存结果,则处理本地缓存结果输出参数(只有存储过程会走)
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 2. 没有缓存结果,则从数据库查询结果
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 查询堆栈数 -1
      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; // key:1463130193:-1799038108:com.itheima.mapper.UserMapper.findByCondition:0:2147483647:SELECT id, name FROM  user WHERE id = ?:1:development
    // 首先向本地缓存中存入一个 ExecutionPlaceholder 的枚举类占位 value
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 3. 执行doQuery方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 执行完成移除这个key
      localCache.removeObject(key);
    }
    // 4. 查询结果存入缓存中
    localCache.putObject(key, list);
    // 如果 MappedStatement 的类型为 CALLABLE,则向 localOutputParameterCache 缓存中存入 value 为 parameter 的缓存
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
}
  1. 当获取到结果得时候,就要存到二级缓存中,这时候并不是真的存,下面会继续解释
public class CachingExecutor implements Executor {

  private final Executor delegate;
  ...
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取二级缓存,需要在配置文件中配置 <cache></cache> 标签
    //   <select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User" flushCache="true">
    Cache cache = ms.getCache();// SynchronizeCache 是包装类,实际调用是 PerpetualCache
    if (cache != null) {
       ...
        if (list == null) {
          // 1. 委托给 BaseExecutor 执行
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 2. 将查询结果,要存到二级缓存中(注意:此处只是存到map集合中,没有真正存到二级缓存中)
          tcm.putObject(cache, key, list); 
        }
        return list;
      }
    }
    // 委托给 BaseExecutor 执行
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  1. TransactionalCacheManager 通过 cache 获取对应的 TransactionalCache,如果没有则新建,然后将结果存入缓存
/**
 * @author Clinton Begin
 * 事务缓存管理器
 */
public class TransactionalCacheManager {

  // Cache(原生的 PertualCache) 与 TransactionalCache(队员生的包装对象) 的映射关系表
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  ...
  public void putObject(Cache cache, CacheKey key, Object value) {
    // 1. 直接存入 TransactionalCache 的缓存中,其中 key 为 1463130193:-1799038108:com.itheima.mapper.UserMapper.findByCondition:0:2147483647:SELECT id, name FROM  user WHERE id = ?:1:development
    getTransactionalCache(cache).putObject(key, value);
  }
  
  private TransactionalCache getTransactionalCache(Cache cache) {
    // 从映射表中获取 TransactionalCache
    return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
  }
}
  1. 实际 TransactionalCache 存入时,只会存入到 entriesToAddOnCommit 这个 Map 缓存中,并非真实的缓存对象 delegate
public class TransactionalCache implements Cache {
 
  // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
  private final Map<Object, Object> entriesToAddOnCommit;
  ...
  @Override
  public void putObject(Object key, Object object) {
    // 1. 将键值对存入到 entriesToAddOnCommit 这个Map中中,而非真实的缓存对象 delegate 中
    entriesToAddOnCommit.put(key, object);
  }
}
  1. 查询到数据后,必须执行 commit() 操作,二级缓存才会真正生效,下面来看看 commit() 方法是什么时候把数据放入二级缓存
public class CacheTest {
  /**
   * 测试二级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {
    ... 
    // 1. 必须要调用sqlSession的commit方法或者close方法,才能让二级缓存生效
    sqlSession1.commit();
    ...
  }
}
  1. commit() 操作的提交,也是交由 CachingExecutor 来执行
public class DefaultSqlSession implements SqlSession {

  private final Executor executor;
  ...
  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      // 1. 执行 commit() 方法
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. 除了 CachingExecutor 委派 SimpleExecutor 执行真正的 commit(),与二级缓存相关最值得关注的是 TransactionalCacheManagercommit() 操作
public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    // 1. 事务缓存管理器
    tcm.commit();
  }
}
  1. TransactionalCacheManager 执行 commit() 时,会遍历 transactionalCaches 也来执行 commit() 操作
/**
 * @author Clinton Begin
 * 事务缓存管理器
 */
public class TransactionalCacheManager {

  // Cache(原生的 PertualCache) 与 TransactionalCache(队员生的包装对象) 的映射关系表
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  ...
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      // 1. 执行事务缓存提交
      txCache.commit();
    }
  }
}
  1. TransactionalCachecommit(),会将 entriesToAddOnCommitentriesMissedInCache 真正刷入二级缓存的 PertualCache 中。首先遍历 entriesToAddOnCommit 把在 Map 中输入插入真正的缓存后,再将 entriesMissedInCache 不在 entriesToAddOnCommit 中的数据也刷入二级缓存中,只不过对应 key 的值为 null
public class TransactionalCache implements Cache {
  /**
   * 委托的 Cache 对象。
   *
   * 实际上,就是二级缓存 Cache 对象。
   */
  private final Cache delegate;
  /**
   * 提交时,清空 {@link #delegate}
   *
   * 初始时,该值为 false
   * 清理后{@link #clear()} 时,该值为 true ,表示持续处于清空状态
   */
  private boolean clearOnCommit;
  
  // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
  private final Map<Object, Object> entriesToAddOnCommit;
  // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
  private final Set<Object> entriesMissedInCache;
  ...
  public void commit() {
    // 如果 clearOnCommit 为 true ,则清空 delegate 缓存
    if (clearOnCommit) {
      delegate.clear();
    }
    // 1. 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中
    flushPendingEntries();
    // 重置
    reset();
  }
  
  /**
   * 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
   */
  private void flushPendingEntries() {
    // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      // 2. 在这里真正的将 entriesToAddOnCommit 的对象逐个添加到 delegate 中,只有这时,二级缓存才真正的生效
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    // 3. 将 entriesMissedInCache 刷入 delegate 中
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }
}
  1. 完成 commit() 后,执行第二次 selectOne() 时,因为数据已经在二级缓存中,所以就能查找到数据,这就解答了第 2 个问题,为什么要在 commit() 时拿到数据,因为第一次存的时候只是放在个临时 Map 中,没真正存到二级缓存
  2. 总结
  • 需要 commit() 才会放入二级缓存
    在这里插入图片描述
  • 流程总结
    在这里插入图片描述

更新方法不会清空二级缓存

  1. 问题:
  • update() 方法为什么不会清空二级缓存?
  1. 首先在测试 case 第一次 selectOne()commit() 后面增加一个 update() 操作
public class CacheTest {
  /**
   * 测试二级缓存
   */
  @Test
  public void secondLevelCacheTest() throws IOException {
    ...
    // 发起第一次查询,查询ID为1的用户
    User user1 = sqlSession1.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);

    // 必须要调用sqlSession的commit方法或者close方法,才能让二级缓存生效
    sqlSession1.commit();

    // 更新操作,清空了 2 级缓存
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
    User user = new User();
    user.setId(1L);
    user.setName("tom");
	
	// 1. 增加更新操作
    sqlSession3.update("com.itheima.mapper.UserMapper.updateUser",user);
    sqlSession3.commit(); // 只有事务提交,才会清除二级缓存
    ...
  }
}
  1. 执行 update() 时,SqlSession 创建 MappedStatement 后,实际还是交由 CachingExecutor 来执行
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 1. 执行 update() 操作
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutorupdate() 时,进行二级缓存的清空,也就是委派 TransactionalCacheManager 进行 clear() 操作
public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  ...
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // 1. 执行二级缓存的清空
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      // 2. 二级缓存的清空
      tcm.clear(cache);
    }
  }
}
  1. TransactionalCacheManager 根据原生 Cache,拿到 TransactionalCache 执行 clear() 操作
/**
 * @author Clinton Begin
 * 事务缓存管理器
 */
public class TransactionalCacheManager {

  // Cache(原生的 PertualCache) 与 TransactionalCache(队员生的包装对象) 的映射关系表
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  ...
  public void clear(Cache cache) {
    // 1. 获取 TransactionalCache 对象,并调用该对象的 clear 方法,下同
    getTransactionalCache(cache).clear();
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 从映射表中获取 TransactionalCache
    return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
  }
}
  1. update() 方法的二级缓存清理实际只是把属性 clearOnCommit 标记为 true,同时把 entriesToAddOnCommit 这个 Map 清空了,但是由于第一次 selectOne() 的时候,已经 commit() 了,所以落到了真正的二级缓存中,只清空了个临时事务缓存
public class TransactionalCache implements Cache {
  /**
   * 提交时,清空 {@link #delegate}
   *
   * 初始时,该值为 false
   * 清理后{@link #clear()} 时,该值为 true ,表示持续处于清空状态
   */
  private boolean clearOnCommit;
  // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
  private final Map<Object, Object> entriesToAddOnCommit;
  ...
  @Override
  public void clear() {
    // 1. 标记 clearOnCommit 为 true
    clearOnCommit = true;
    // 2. 清空 entriesToAddOnCommit
    entriesToAddOnCommit.clear();
  }
}
  1. update() 方法执行完成后,开始执行 commit() 方法,SqlSession 再次委派 CachingExecutor 执行 commit() 方法
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  @Override
  public void commit() {
    commit(false);
  }
  
  @Override
  public void commit(boolean force) {
    try {
      // 1. 执行 commit() 方法
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}
  1. CachingExecutor 委派 SimpleExecutor 执行完 commit() 方法后,再调用 TransactionalCacheManager 执行 commit() 方法
public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  ...
  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    // 1. 和二级缓存相关
    tcm.commit();
  }
}
  1. TransactionalCacheManager 遍历 transactionalCaches,执行 commit() 方法
/**
 * @author Clinton Begin
 * 事务缓存管理器
 */
public class TransactionalCacheManager {

  // Cache(原生的 PertualCache) 与 TransactionalCache(对原生的包装对象) 的映射关系表
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  ...
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      // 1. TransactionalCache 包装对象执行 commit() 方法
      txCache.commit();
    }
  }
}
  1. 由于 update() 已经把 clearOnCommit 属性标记为 true,这时候,就真的把对应的二级缓存都清空了
public class TransactionalCache implements Cache {

  /**
   * 委托的 Cache 对象。
   *
   * 实际上,就是二级缓存 Cache 对象。
   */
  private final Cache delegate;
  ...
  public void commit() {
    // 如果 clearOnCommit 为 true ,则清空 delegate 缓存
    if (clearOnCommit) {
      // 1. 真正清理二级缓存
      delegate.clear();
    }
    // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中
    flushPendingEntries();
    // 重置
    reset();
  }
}
  1. 总结
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值