[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)對於頻率且大數據量的查詢,防止一級緩存佔用的內存過大。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章