Mybatis源码学习(七)mybatis缓存详解

前言

MyBatis 包含一个非常强大的查询缓存特性,使用缓存可以使应用更快地获取数据,避免频繁的数据库交互.让我们开始学习一下mybatis的缓存吧.

1. 一级缓存

  • 一级缓存默认会启用,想要关闭一级缓存可以在select标签上配置flushCache=“true”
  • 一级缓存存在于 SqlSession 的生命周期中,在同一个 SqlSession 中查询时, MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map对象中。如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当 Map 缓存对象中己经存在该键值时,则会返回缓存中的对象
  • 任何的 INSERT 、UPDATE 、 DELETE 操作都会清空一级缓存

1.1 一级缓存对象的初始化

BaseExecutor中维护的一级缓存对象.在BaseExecutor维护了两个一级缓存的对象.在构造函数里进行对象的初始化.

/**
   * 本地缓存, 一级缓存
   */
  protected PerpetualCache localCache;
  /**
   * 本地输出参数的一级缓存
   */
  protected PerpetualCache localOutputParameterCache;

/**受保护的构造方法
   * @param configuration       核心配置对象
   * @param transaction         事务
   */
  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;
  }

1.2 一级缓存对象的使用

查询时先从缓存中查询,没有才进行数据库查询,查询完毕会将查询到的结果放入缓存中.方便后续使用.
在这里插入图片描述
在这里插入图片描述

1.3 缓存key的生成方式

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //创建一个cacheKey
    CacheKey cacheKey = new CacheKey();
    //使用mappedStatement的id,作为计算缓存key值的一部分
    cacheKey.update(ms.getId());
    //使用分页对象的offset,作为计算缓存key值的一部分
    cacheKey.update(rowBounds.getOffset());
    //使用分页对象的limit,作为计算缓存key值的一部分
    cacheKey.update(rowBounds.getLimit());
    //使用原始的sql语句,作为计算缓存key值的一部分
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      //使用sql的输入参数,作为计算缓存key值的一部分
      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);
      }
    }
    //使用环境参数,作为计算缓存key值的一部分
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

2. 二级缓存

  • 二级缓存存在于 SqlSessionFactory 的生命周期中,可以理解为跨sqlSession;缓存是以namespace为单位的,不同namespace下的操作互不影响.
  • setting参数 cacheEnabled,这个参数是二级缓存的全局开关,默认值是 true,如果把这个参数设置为 false,即使有后面的二级缓存配置,也不会生效
  • 要开启二级缓存,你需要在你的 mapper.xml中添加配置

2.1 二级缓存对象的初始化

如果Mapper.xml中配置了二级缓存标签,再构建MappedStatement时会创建一个二级缓存对象
在这里插入图片描述
CachingExecutor中维护了一个事务缓存管理器,来管理二级缓存.
在这里插入图片描述

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  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();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

2.2 二级缓存对象的使用

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //从statement中获取缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      //如果statement中配置了<cache>
      //根据配置清除缓存
      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;
      }
    }
    //未配置二级缓存,调用代理类查询并返回
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

2.3 缓存key的生成方式

缓存key的生成方式和一级缓存一样

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //创建一个cacheKey
    CacheKey cacheKey = new CacheKey();
    //使用mappedStatement的id,作为计算缓存key值的一部分
    cacheKey.update(ms.getId());
    //使用分页对象的offset,作为计算缓存key值的一部分
    cacheKey.update(rowBounds.getOffset());
    //使用分页对象的limit,作为计算缓存key值的一部分
    cacheKey.update(rowBounds.getLimit());
    //使用原始的sql语句,作为计算缓存key值的一部分
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      //使用sql的输入参数,作为计算缓存key值的一部分
      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);
      }
    }
    //使用环境参数,作为计算缓存key值的一部分
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

mybatis中缓存对比

在这里插入图片描述

总结

mybatis每次查询时都会优先从缓存中获取数据先查二级缓存,再查一级缓存二级缓存以namespace为单位的,是SqlSession共享的,容易出现脏读,建议避免使用二级缓存一级缓存是SqlSession独享的,建议开启
在这里插入图片描述

喜欢的小伙伴请动动小手关注和点赞吧,也可留言一起探讨怎样更好的学习源码!!!

mybatis带中文注释源码地址

github地址

文章链接

Mybatis源码学习(一)初探执行流程
Mybatis源码学习(二)配置文件解析到SqlSessionFactory构建
Mybatis源码学习(三)SqlSession详解
Mybatis源码学习(四)自定义Mapper方法执行流程
Mybatis源码学习(五)Executor和StatementHandler详解
Mybatis源码学习(六)结果集自动封装机制
Mybatis源码学习(七)mybatis缓存详解
Mybatis源码学习(八)Mybatis设计模式总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值