Mybatis源码解析之缓存机制(一):一级缓存

Mybatis源码解析之核心类分析
Mybatis源码解析之初始化分析
Mybatis源码解析之执行流程解析
Mybatis源码解析之数据库连接和连接池
Mybatis源码解析之事务管理

一、CacheKey

CacheKey是每次查询操作的特征值抽象而成的类,用于区别查询是否相同,从而从cache中找到对应的结果。
当下列元素相同时,我们认为是相同的查询。
(1)statementId
(2)要求的查询结果集的范围(RowBounds的offset和limit)
(3)传给statement的sql语句
(4)传给statement的参数集
但是Cachekey并不是简单的直接由这四个元素作为成员变量组成的一个类,而是进一步抽象,并没有限制特征值的数量和类型。

CacheKey的本质是判断相等性,也就是equals方法,而equals方法相等的前提是hashcode相等,CacheKey核心是其经典的hashcode算法。
CacheKey通过update方法加入一个特征值对象。

public void update(Object object) {
  int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
 //特征值数量
  count++;
  //特征值的hashcode之和
  checksum += baseHashCode;
  baseHashCode *= count;
 //hashcode = 原来的hashcode * 扩展因子(37) + 新特征值的hashcode
  hashcode = multiplier * hashcode + baseHashCode;

  updateList.add(object);
}

可以看出,不同CacheKey对象的hashcode的碰撞率可以控制在一个极小的概率上。

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

再看equal方法,判断特征集集合的相等性只是最后一步,如果两个对象不等,只有极小的可能需要对特征值集合进行比较。

二、PerpetualCache

一级缓存使用PerpetualCache对象进行表示。
PerpetualCache除了一个id外,保存缓存的属性cache只是一个HashMap,private Map cache = new HashMap();CachKey作为map的key值,查询结果作为map的value值。 所有对缓存的操作实际上就是对HashMap的操作。

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

二、一级缓存的处理

一级缓存又称为本地缓存。
一级缓存不需要任何配置,而是强制开启的,无法取消。不过动态sql的flushc参数可以强制清除所有一级缓存。

1. query方法

@Override
public  List 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.");
  }
  //flush为true,强制清除缓存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List list;
  try {
    queryStack++;
    //从缓存中进行查找
    list = resultHandler == null ? (List) localCache.getObject(key) : null;
    if (list != null) {
     //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;
}

private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 List 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. 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);
}

可以看到,执行update方法会清空本地缓存。

3. commit方法

@Override
public void commit(boolean required) throws SQLException {
  if (closed) {
    throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}

可以看到,执行commit方法也会清空本地缓存。

3. close方法

@Override
public void close(boolean forceRollback) {
  try {
    try {
      rollback(forceRollback);
    } finally {
      if (transaction != null) {
        transaction.close();
      }
    }
  } catch (SQLException e) {
    // Ignore.  There's nothing that can be done at this point.
    log.warn("Unexpected exception on closing transaction.  Cause: " + e);
  } finally {
    transaction = null;
    deferredLoads = null;
    localCache = null;
    localOutputParameterCache = null;
    closed = true;
  }
}

可以看到,close方法也会将缓存删除。

三、一级缓存的性能

从前面的分析可以看到,一级缓存是一个粗粒度的缓存,设计的也比较简单。仅仅只是一个HashMap,也没有对HashMap的大小进行管理,也没有缓存更新和过期的概念。
这是因为一级缓存的生命周期很短,不会存活多长时间。
(1)每次调用update方法(insert、delete、update的sql),都会将一级缓存清空。
(2)一级缓存是SqlSession级别的,SqlSession一旦关闭,对应的一级缓存也就不会存在。
(3)可以通过BaseExecutor#clearLocalCache()手动清空缓存。
因此,一下情况时要控制好SqlSession的生存时间,必要时手动清除一级缓存
(1)对于数据的时效性准确性要求比较高的查询,防止一级缓存的长时间存活导致脏数据的读取。
(2)对于频率且大数据量的查询,防止一级缓存占用的内存过大。

你可能感兴趣的:(Mybatis,Mybatis源码解析)