[java学习笔记]MyBatis源码学习笔记(五) 一级缓存

Mybatis中的多级缓存

一级缓存

一、Cachekey

要了解一级缓存,先要了解一个类:CacheKey,它是查询条件的抽象封装体,也就是说,判断一个条件是否是之前查过的,那么就是通过判断Cachekey的相等性也就是Equals方法了
当下列特征值相同时,我们认为是相同的查询。

  1. statementId
  2. 要求的查询结果集的范围(RowBounds的offset和limit)
  3. 传给statement的sql语句
  4. 传给statement的参数集

特征值是通过update方法来添加到CacheKey对象内的

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;
}
二、PerpetualCache

PerpetualCache除了一个id外,保存缓存的属性cache只是一个HashMap

private Map<Object, Object> cache = new HashMap<Object, Object>();

CachKey作为map的key值,查询结果作为map的value值。 所有对缓存的操作实际上就是对HashMap的操作。

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

    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
  return list;
}

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

那么什么时候会需要清除缓存呢?首先从上面的源码看来,如果已经用数据库查询了,那么就会把之前的缓存清除
其他情况是否还能出发缓存清除呢?
update方法(实际上delete、insert都会清除缓存)

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

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

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

三、一级缓存的性能
从前面的分析可以看到,一级缓存是一个粗粒度的缓存,设计的也比较简单。仅仅只是一个HashMap,也没有对HashMap的大小进行管理,也没有缓存更新和过期的概念。
这是因为一级缓存的生命周期很短,不会存活多长时间。

  1. 每次调用update方法(insert、delete、update的sql),都会将一级缓存清空。
  2. 一级缓存是SqlSession级别的,SqlSession一旦关闭,对应的一级缓存也就不会存在。(会话结束,事务提交或回滚)
  3. 可以通过BaseExecutor#clearLocalCache()手动清空缓存。
    因此,一下情况时要控制好SqlSession的生存时间,必要时手动清除一级缓存
    (1)对于数据的时效性准确性要求比较高的查询,防止一级缓存的长时间存活导致脏数据的读取。
    (2)对于频率且大数据量的查询,防止一级缓存占用的内存过大。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值