Mybatis源码学习(7)--mybatis缓存分析

戏子无情 提交于 2021-01-30 03:17:42

mybatis缓存分为一级缓存和二级缓存,一级缓存作用当个sqlsession,如果sqlSession关闭了,那么缓存就失效了,二级缓存作用在mapper映射文件。默认二级缓存处理类是CachingExecutor,一级缓存处理类是BaseExecutor,调用query方法时会先从二级缓存中查找结果,再从一级缓存中找,都找不到再查询数据库。

mybatis缓存级别

一级缓存

作用域:当个sqlsession,仅仅对一个会话中的数据进行缓存,不能关闭,调用query查询时,将查询结果缓存在BaseExecutor.localCache变量中,BaseExecutor对象随sqlSession创建而创建,当sqlSession销毁时,BaseExecutor随之消失,缓存消失。

sqlSession从open()到close()中间整个范围就是单个sqlSession作用域,作用域区间如果调用相同的查询方法,并且参数相同,那么只会执行一次sql。

//打开一个sqlsession
SqlSession session = ssf.openSession();

UserMapper userMapper = session.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap();
map.put("name","loli");
List<User> list = userMapper.selectByMap(map);
List<User> list2 = userMapper.selectByMap(map);

//关闭sqlsession
session.close();

二级缓存

作用域:跨sqlsession,全局sqlSession,也叫映射器缓存,默认开启,可管理开关,通过mybatis系统配置可以设置,默认为true,但是还需在每个mapper.xml映射文件中添加<cache>标签开启当前mapper映射文件的二级缓存。

<configuration >
......
    <settings>
        <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true。-->
        <setting name="cacheEnabled" value="true"></setting>
    </settings>
......
</configuration>

在解析mapper.xml时,会读取<cache>节点配置,如果没有配置,则当前mapper不开启二级缓存,否则开启二级缓存。

根据<cache>节点配置封装Cache对象,id为namespace值,所以一个mapper.xml虽然有多个<select><delete>等sql,但是每个sql封装的MappedStatement对象引用的是同一个cache变量。相当于同一mapper.xml文件下sql封装的MappedStatement对象,引用同一个cache对象。

<cache>节点配置属性介绍请详见官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

<mapper namespace="com.lu.UserMapper" >

   <cache
  type="com.lu.xxxxCache"
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"
  blocking="true" />

</mapper>

所以二级缓存跟sqlsession没有关系,不会随着sqlsession销毁而清除,属于全局缓存,不同sqlsession调用同一个mapper,同一个sql,同样传参,sql只会执行一遍。

 

例子:如果UserMapper.xml开启了二级缓存,虽然open了两个sqlsession,但是调用相同的UserMapper.xml,相同selectByMap查询方法,相同map参数,所以也只会执行一遍sql。

        SqlSession session1 = ssf.openSession();
        SqlSession session2 = ssf.openSession();

        UserMapper userMapper1 = session1.getMapper(UserMapper.class);
        UserMapper userMapper2 = session2.getMapper(UserMapper.class);

        Map<String, Object> map = new HashMap();
        map.put("name","loli");
        List<User> list1 = userMapper1.selectByMap(map);
        List<User> list2 = userMapper2.selectByMap(map);
 
        session1.close();
        session2.close();

 

缓存执行流程图

一级缓存

缓存对象初始化

缓存对象PerpetualCache,缓存其实就是将查询结果存在PerpetualCache类中Map cache属性中。

代码分析:

所有缓存对象存在cache map中,map的key表示查询SQL封装的CacheKey对象,value表示查询结果。 

public class PerpetualCache implements Cache {

  private final String id;
  //map存储cache对象
  private Map<Object, Object> cache = new HashMap<>();

  .......
}

 

PerpetualCache localCache对象在什么时候初始化的呢?

PerpetualCache localCache是在调用openSession方法生成sqlsession时初始化。

SqlSession session = ssf.openSession();

org.apache.ibatis.session.Configuration#newExecutor

这些Executor类都继承自BaseExecutor基类,在new Executor对象时,会初始化BaseExecutor对象,BaseExecutor对象包含PerpetualCache localCache属性,此时就会初始化localCache一级缓存对象。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 默认执行器 SIMPLE,可配置defaultExecutorType指定执行器
    // SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
    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);
    }
    //二级缓存开关,默认开启true
    //如果开启二级缓存,则封装executor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //封装自定义的Executor插件类
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

org.apache.ibatis.executor.BaseExecutor#BaseExecutor 

public abstract class BaseExecutor implements Executor {

  protected PerpetualCache localCache;
  
  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

}

缓存使用

执行insert/delete/update操作

会先清除缓存,在执行增删改操作。

代码分析:

org.apache.ibatis.session.defaults.DefaultSqlSession#insert(java.lang.String, java.lang.Object)

/**
   * 插入
   * @return
   */
  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  /**
   * 更新操作
   * @return
   */
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

org.apache.ibatis.executor.BaseExecutor#update

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //清除一级缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

执行query查询操作

执行查询操作时,步骤如下

  1. 构建CacheKey key对象
  2. 调用localCache.getObject(key)从缓存中获取查询结果,如果存在则直接返回结果。
  3. 如果缓存中没有匹配key,调用localCache.putObject(key, EXECUTION_PLACEHOLDER)方法在一级缓存占位,再查询数据库得到结果,移除缓存中存的占位数据
  4. 调用localCache.putObject(key, list)缓存查询结果

代码分析 :

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据key=statement=com.lu.UserMapper.selectByMap,从configuration对象mappedStatements(map)中获取MappedStatement对象,
      // MappedStatement对象封装了<select id="selectByMap">所有配置信息
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

org.apache.ibatis.executor.BaseExecutor#query

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //如果sql是动态sql,如<if> ${id}标签会被替换成参数,如果是#{}则在后面才会替换参数
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //new一个缓存对象,ms必须是同一个,参数相同,分页相同,sql相同
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

@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++;
      //localCache一级缓存,判断缓存中是否存在
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //<select statementType="CALLABLE">类型,是否执行存储过程
        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;
  }


  /**
   * 查询数据库
   * @return
   * @throws SQLException
   */
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //在一级缓存调用put缓存占位
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //查询
      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;
  }

二级缓存

缓存对象初始化

默认缓存对象PerpetualCache,缓存其实就是将查询结果存在PerpetualCache类中Map cache属性中,也可自己指定其他缓存实现类。

二级缓存对象和一级缓存对象一样,但是二级缓存采用包装者模式封装了PerpetualCache对象。

PerpetualCache localCache

在什么时候初始化对象的呢?

解析mapper映射文件<cache>节点配置,会初始化cache缓存对象,并采用包装者模式封装PerpetualCache缓存对象。

<cache>节点配置属性介绍请详见官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

<mapper namespace="com.lu.UserMapper" >
 <cache
  type="com.lu.xxxxCache"
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"
  blocking="true" />

</mapper>

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement

  /**
   * 二级缓存配置封装对象
   * @param context
   */
  private void cacheElement(XNode context) {
    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();
      //根据配置参数构建cache缓存对象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache

读取<cache>节点配置,给CacheBuilder属性赋值。调用build()方法包装cache对象。

/**
   *   构建cache包装类
   * @param typeClass   cache实现类,可自定义实现类
   * @param evictionClass  清除缓存策略 LRU FIFO  SOFT  WEAK
   * @param flushInterval  刷新缓存间隔时间
   * @param size           缓存个数 默认1024
   * @param readWrite      是否需要读写,如果为true,就需要序列化缓存数据
   * @param blocking       读取缓存是否需要上锁
   * @param props          缓存相关配置信息
   * @return
   */
  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();//构建cache包装类
    //保存缓存
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

org.apache.ibatis.mapping.CacheBuilder#build 

这里就是解析是否指定缓存实现类,默认底层缓存实现类是PerpetualCache,

如果使用默认实现类PerpetualCache,则会采用装饰着封装PerpetualCache对象。

如果自定义了缓存实现类,则只会在自定义类上装饰一层LoggingCache对象。

public Cache build() {
    //判断<cache type="class">是否自定义缓存实现类,
    // 如果type为空,则使用implementation=PerpetualCache默认实现类,否则implementation=自定义类
    setDefaultImplementations();
    //根据implementation变量,反射构造cache对象
    Cache cache = newBaseCacheInstance(implementation, id);
    //<cache>
    //  <property name="" value=""></property>
    //</cache>
    //property赋值给cache对象
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //如果二级缓存实现类是默认的PerpetualCache类,则使用mybatis默认的包装流程
    //否则直接使用自定义cache实现类
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //包装cache对象
      cache = setStandardDecorators(cache);
      //判断自定义缓存实现类是否继承自LoggingCache,不是,则LoggingCache(cache)包装
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators

装饰者封装了多层cache

  • cache=new PerpetualCache(cache)  缓存真正实现
  • cache=new ScheduledCache(cache)   定义缓存清除时间t1,在操作缓存时,判断当前时间-最后操作缓存时间,如果大于t1,则清除缓存
  • cache=new SerializedCache(cache) 序列化缓存对象
  • cache=new LoggingCache(cache)  记录缓存命中率日志
  • cache=new SynchronizedCache(cache) 所有操作缓存方法上加synchronized关键字
  • cache=new BlockingCache(cache)  使用ReentrantLock操作锁代码
/**
   * 封装cache对象
   * cache=ScheduledCache(cache)
   * cache=SerializedCache(cache)
   * cache=LoggingCache(cache)
   * cache=SynchronizedCache(cache)
   * cache=BlockingCache(cache)
   * @param cache
   * @return
   */
  private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      //<cache flushInterval="1000">配置
      //ScheduledCache不会定时删除缓存,每次get/put等操作缓存时
      // if( 当前系统时间 - 上一次清除时间 > flushInterval时间 ) 则清除缓存,并设置清除时间=系统时间
      // else 向下传递
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      //<cache readOnly="false">配置
      //序列化缓存对象
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      //记录日志
      cache = new LoggingCache(cache);
      //同步缓存操作
      cache = new SynchronizedCache(cache);
      //<cache blocking="true">配置
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

缓存使用

执行insert/delete/update操作

默认先清除相应mapper上的缓存,在执行增删改操作。

代码分析:

org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)

org.apache.ibatis.executor.CachingExecutor#update

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //清除缓存
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }  

/**
   * 清除缓存
   * @param ms
   */
  private void flushCacheIfRequired(MappedStatement ms) {
    //获取mapper映射器缓存对象
    Cache cache = ms.getCache();
    //根据sql节点上flushCache值,判断是否刷新缓存,默认:增删改true,查询false
    if (cache != null && ms.isFlushCacheRequired()) {
      //清除缓存
      tcm.clear(cache);
    }
  }

 

执行query查询操作

执行查询操作,缓存调用过程大致如下:

  1. 创建CacheKey key缓存对象
  2. ms.getCache()获取mapper映射文件对应的二级缓存对象(经过多层包装的cache对象)
  3. flushCacheIfRequired(ms)判断是否要清除mapper的二级缓存,逻辑同一级缓存一样,根据<select flushCache="true">节点上flushCache值判断
  4. ms.isUseCache()判断当前sql是否需要缓存,根据<select useCache="true">useCache值判断,为true表示使用缓存,否则不缓存
  5. tcm.getObject()查询事务管理缓存tcm,存在则直接缓存,不存在查询数据库,将结果缓存在事务管理缓存tcm中
  6. 调用commit()方法,将事务管理缓存tcm中缓存值保存在真正缓存对象中。

代码分析:

org.apache.ibatis.executor.CachingExecutor#query

  /**
   * 二级缓存
   */
  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //如果sql是动态sql,如<if> ${id}标签会被替换成参数,如果是#{}则在后面才会替换参数
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //new一个缓存对象,ms必须是同一个,参数相同,分页相同,sql相同
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    //二级缓存对象,在解析build mapper.xml中<cache>标签时构建的cache对象
    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;
      }
    }
    //不使用二级缓存,delegate实现类是SimpleExecutor类,delegate在Test1 main方法中获取sqlSession时赋值的
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

 

事物缓存

上面使用二级缓存时,查询结果是先存到事务管理缓存tcm,而不是直接存储在PerpetualCach cache底层缓存对象中,是因为mybatis存在事物管理。

 

将查询结果直接缓存在PerpetualCach cache底层map中不可以么?

事物缓存管理是为了防止出现如下情况,下图操作失败,插入的a数据在数据库中会回滚,但是二级缓存已经缓存了结果a,这种操作是不正确的

那么mybatis是如何实现的呢?

 

我们看下mybatis事物缓存是如何实现的,回顾CachingExecutor类查询操作中缓存实现

org.apache.ibatis.executor.CachingExecutor#query


  //管理事物缓存
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();


  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    //二级缓存对象,在解析build mapper.xml中<cache>标签时构建的cache对象
    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;
      }
    }
    //不使用二级缓存,delegate实现类是SimpleExecutor类,delegate在Test1 main方法中获取sqlSession时赋值的
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

TransactionalCacheManager类是用来管理事物缓存的,有commit和rollback两个方法,但是底层实现是TransactionCache类。

public class TransactionalCacheManager {

  //key=mapper对应的cache对象
  //value=缓存当前事物中,对key中mapper所有查询sql及结果封装的TransactionalCache对象
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  /**
   * 调用TransactionalCache.clear()方法
   * 清除当前事物中待提交的缓存记录
   * @param cache
   */
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  /**
   * 调用TransactionalCache.getObject()方法
   * 根据key获取缓存value结果
   * @param cache
   * @param key
   * @return
   */
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  /**
   * 调用TransactionalCache.getObject()方法
   * 将key,value缓存到TransactionalCache#entriesToAddOnCommit待提交缓存map属性中
   * @param cache
   * @param key
   * @param value
   */
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  /**
   * 提交事务
   */
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  /**
   * 事物回滚
   */
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  /**
   * transactionalCaches根据key获取value
   * 如果不存在则new TransactionalCache(PerpetualCach cache)
   * @param cache
   * @return
   */
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

 TransactionalCache类有两个重要的属性entriesToAddOnCommit,entriesMissedInCache,在事物中查询的结果会缓存到这两个属性中,

  • entriesToAddOnCommit:保存待提交的缓存
  • entriesMissedInCache:未命中缓存集合,防止缓存穿透(防止一直访问数据库中没有的数据,导致一直查询缓存和数据库

在事物未提交前,会将当前事物中所有查询结果临时存到entriesToAddOnCommit,entriesMissedInCache属性map中。

事物提交成功后会调用commit()方法,将所有临时缓存对象保存到底层真正PerpetualCach cache属性map中。

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;
  //待提交的缓存
  private final Map<Object, Object> entriesToAddOnCommit;
  //未命中缓存集合,防止缓存穿透(防止一直访问数据库中没有的数据,导致一直查询缓存和数据库)
  private final Set<Object> entriesMissedInCache;

  /**
   * 查询缓存
   * @param key The key
   * @return
   */
  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

/**
   * 将缓存对象临时保存在entriesToAddOnCommit属性map中
   * @param key    cache缓存对象,包含查询sql,参数,分页等
   * @param object 查询结果
   */
  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }


 /**
   * 提交缓存
   */
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    //刷新缓存,将待提交的缓存put到真实缓存map对象中
    flushPendingEntries();
    //重置清空属性值
    reset();
  }

  /**
   * 回滚
   * 删除未命中的缓存
   *
   */
  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  /**
   * 清除属性值
   */
  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  /**
   * 刷新缓存
   * 将待提交的缓存entriesToAddOnCommit中值保存到底层真正存储缓存PerpetualCach cache属性map中
   * 将未命中的缓存entriesMissedInCache中值保存,设置结果设置为null
   */
  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      //put到真正缓存中
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      //把未命中的put到
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  /**
   * 将未命中的缓存从底层缓存map中删除
   */
  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

}

 

 

缓存对象

如何判断缓存存在,根据什么条件判断是查询同一个sql,判断namespace,还是sql语句?

判断代码如下,key表示CacheKey类

public class PerpetualCache implements Cache {
 //map存储cache对象
  private Map<Object, Object> cache = new HashMap<>();

 public Object getObject(Object key) {
    return cache.get(key);
  }

}

缓存对象判断逻辑代码分析:

org.apache.ibatis.executor.BaseExecutor#query

执行查询方法时,通过调用query方法,执行createCacheKey()创建cache对象。

调用cacheKey.update()方法,传入重要参数MappedStatement id,sql,分页,参数值,保存到CacheKey类updateList属性中。

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //动态sql参数赋值
    BoundSql boundSql = ms.getBoundSql(parameter);
    //创建cache缓存对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }


public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    //mapper id+sql id 全名
    //com.lu.UserMapper.selectByMap
    cacheKey.update(ms.getId());
    //分页参数
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    //查询sql,动态sql ${}替换成参数,#{}为?
    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);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      //数据库环境
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

CacheKey类,通过调用重写的equals()方法,循环判断updateList中值是否相等,来判断是否是同一个缓存对象。

public class CacheKey implements Cloneable, Serializable {

.......
  
  public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;

    updateList.add(object);
  }


  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (!(object instanceof CacheKey)) {
      return false;
    }

    final CacheKey cacheKey = (CacheKey) object;

    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    if (checksum != cacheKey.checksum) {
      return false;
    }
    if (count != cacheKey.count) {
      return false;
    }

    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (!ArrayUtil.equals(thisObject, thatObject)) {
        return false;
      }
    }
    return true;
  }

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!