mybatis一级缓存详解

1.mybatis一级缓存

一级缓存: sqlSession缓存 [会话缓存]。默认开启,用户不能关闭(有方法让其失效)。通过同一个sqlSession调用同一个查询方法两次,第二次查询走的缓存。

下面我们就看看一级缓存怎么保存的?
在创建sqlSession的时候会创建Executor
BaseExecutor里有个属性localCache就是保存一级缓存内容的

public abstract class BaseExecutor implements Executor {
 
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

打开看PerpetualCache类,看cache熟悉,其实就是个map

public class PerpetualCache implements Cache {
  private final String id;
  private Map<Object, Object> cache = new HashMap<Object, Object>();

接下来看看是怎么判断缓存命中的?
首先看看在BaseExecutor.query()方法在查询时先生成cachekey

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

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());//statmentid
    cacheKey.update(rowBounds.getOffset());//默认是0
    cacheKey.update(rowBounds.getLimit());//默认是 Integer.MAX_VALUE
    cacheKey.update(boundSql.getSql());//sql语句
    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   环境ID
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

根据statmentid,rowbounds,sql,参数生存cachekey

CacheKey这个类是重写了equals方法的

 @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;
  }

首先hashcode,checksum,count必须相等,然后分别对比什么生存key的几个参数(statmentid,rowbounds,sql等),这些条件都满足时才出走缓存

接下来看看缓存什么时候放进去的?

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

在从数据库查询的时候,先是放了个占位符EXECUTION_PLACEHOLDER,然后执行查询,再通过key删除占位符,最后就查询的数据放入一级缓存localCache中(我暂时也不知道为什么要这么设计。)

接下来看看在什么地方从缓存中取得?

  @SuppressWarnings("unchecked")
  @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++;
      //通过cachekey从缓存中取,没取到时才会去查数据库
      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();
      //当设置localCacheScope=statment时,一级缓存就失效了,每次查询完都会清空一级缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

通过cachekey从缓存中取,没取到时才会去查数据库

最后看看什么时候会清空缓存?
1.在进行insert,update,delete时会清空缓存
看看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);
  }

2.在调用sqlSession的close()和clearCache()方法的时候,一级缓存本来就是会话缓存。
3.直接使一级缓存失效,

<setting name="localCacheScope" value="STATMENT"/>
发布了42 篇原创文章 · 获赞 29 · 访问量 2542

猜你喜欢

转载自blog.csdn.net/qq_32314335/article/details/103542847