1.[Mybatis源码]一级缓存、二级缓存

参考博客:https://www.cnblogs.com/happyflyingpig/p/7739749.html

Mybatis的执行器

BaseExecutor执行器使用的是一级缓存

CachingExecutor执行器使用的是二级缓存

1.一级缓存

1.1整体架构图

1.2一级缓存的实现(源码级别)

        一级缓存其实通俗地来讲就是,在sqlsession里面创建一个本地缓存,然后第二次进行相同的查询时候,就不会到数据库里面进行查找,而是从缓存中获取结果集。Mybatis利用HashMap实现缓存

1.2.1一级缓存的存储类PerpetualCache

public class PerpetualCache implements Cache {
  //一级缓存的ID
  private final String id;
  //利用HashMap缓存结果集  
  private Map<Object, Object> cache = new HashMap<Object, Object>();


  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 ReadWriteLock getReadWriteLock() {
    return null;
  }


  @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;
    return getId().equals(otherCache.getId());
  }


  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }


}

Mybatis的内部一级缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。

SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。

1.2.2一级缓存的存储时机(BaseExecutor中的方法)先查缓存,缓存中如果没有,则查询数据库

查询入口query函数(queryStack的作用:用来记录嵌套查询的层数)

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  //检查当前executor是否已经关闭
  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()) {
    //非嵌套查询,并且<select>节点配置的flushCache属性为true时,才会清空一级缓存
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;//增加查询层数
    //查询一级缓存
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      //1.处理结果
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      //其中会调用doQuery方法完成数据库查询
      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;
}

执行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 {
    //执行数据库的查询操作,调用子类方法
    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;
}

2.二级缓存

参考博客:https://blog.csdn.net/luanlouis/article/details/41408341#commentBox

参考博客:https://www.cnblogs.com/happyflyingpig/p/7739749.html

        CachingExecutor是Executor的装饰者,以增强Executor的功能,使其具有缓存查询的功能,这里用到了设计模式中的装饰者模式,CachingExecutor和Executor的接口的关系如下类图所示:

2.1  使用二级缓存需要的配置

     MyBatis对二级缓存的支持粒度很细,它会指定某一条查询语句是否使用二级缓存。

     虽然在Mapper中配置了<cache>,并且为此Mapper分配了Cache对象,这并不表示我们使用Mapper中定义的查询语句查到的结果都会放置到Cache对象之中,我们必须指定Mapper中的某条选择语句是否支持缓存,即如下所示,在<select> 节点中配置useCache="true",Mapper才会对此Select的查询支持缓存特性,否则,不会对此Select查询,不会经过Cache缓存。如下所示,Select语句配置了useCache="true",则表明这条Select语句的查询会使用二级缓存。

<select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" useCache="true">

总之,要想使某条Select查询支持二级缓存,你需要保证:

   1.  MyBatis支持二级缓存的总开关:全局配置变量参数   cacheEnabled=true

   2. 该select语句所在的Mapper,配置了<cache> 或<cached-ref>节点,并且有效

   3. 该select语句的参数 useCache=true

 

 

2.2  二级缓存实现的选择

    MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在<cache  type="">节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成,总之,使用MyBatis的二级缓存有三个选择:

        1.MyBatis自身提供的缓存实现;

        2. 用户自定义的Cache接口实现;

        3.跟第三方内存缓存库的集成;

 

2.2.1 MyBatis自身提供的缓存实现;

2.2.1 二级缓存的配置

①MyBatis支持二级缓存的总开关:全局配置变量参数   cacheEnabled=true

②该select语句所在的Mapper,配置了<cache> 或<cached-ref>节点,并且有效

③该select语句的参数 useCache=true

2.2.2 二级缓存使用的执行器(CachingExecutor类)

①这里的CachingExecutor是装饰类

②当进行查询操作时,执行query函数

③TransactionalCacheManager和TransactionalCache是CachingExecutor依赖的两个组件,TransactionalCacheManager和TransactionalCache的关系如下

 

将查询到的结果集先存储在TransactionalCache类中的private final Map<Object, Object> entriesToAddOnCommit;map中

    TransactionalCache.putObject()方法并没有直接将结果对象记录到其封装的二级缓存中,而是暂时保存在entriesToAddOnCommit集合中,在事务提交时才会将这些结果对象从entriesToAddOnCommit集合添加到二级缓存中。

二级缓存使用的缓存类型

事务提交时执行CachingExecutor执行器的commit方法

执行flushPendingEntries函数将记录添加到二级缓存中

当配置文件中不设置缓存策略时,使用的默认缓存实现类如下图所示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值