天天看點

Mybatis緩存機制概述一級緩存二級緩存

概述

        mybatis的緩存分為一級緩存和二級緩存。

        一級緩存是會話級别,也就是session級别。一級緩存預設開啟,使用者不能手動配置(但也不是絕對,可以通過修改源碼或配置插件的方式修改)。

        二級緩存是應用級别,也就是application級别,這裡可以細粒度的進行控制,對mapper級别進行控制,可以各個mapper使用各自的緩存,也可以配置多個mapper公用一個緩存。二級緩存預設關閉,需要使用者手動配置開啟。

Mybatis緩存機制概述一級緩存二級緩存

一級緩存

        一級緩存是session級别,在一次會話中,sqlsession會使用executor對象來完成一次會話操作,而一個executor維護一個cache對象,在一級緩存中,主要維護的是perpetualCache.

Mybatis緩存機制概述一級緩存二級緩存
public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);
  
  void clear();

  int getSize();
  
  ReadWriteLock getReadWriteLock();

}
           
public class PerpetualCache implements Cache {

  private String id;

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

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

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

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}
           

可以看到PerpetualCache非常簡單,裡面的實作cache的機制就是一個簡單的HashMap

mybatis中最為主要的接口就是sqlsession.這個接口定義了操作資料庫的主要方法。

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);
  
  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  /**
   * Closes the session
   */
  @Override
  void close();

  /**
   * Clears local session cache
   */
  void clearCache();

  /**
   * Retrieves current configuration
   * @return Configuration
   */
  Configuration getConfiguration();

  /**
   * Retrieves a mapper.
   * @param <T> the mapper type
   * @param type Mapper interface class
   * @return a mapper bound to this SqlSession
   */
  <T> T getMapper(Class<T> type);

  /**
   * Retrieves inner database connection
   * @return Connection
   */
  Connection getConnection();
}
           

最常用的sqlsession主要是defaultsqlsession,defaultsqlsession實作了sqlsession接口

查詢源碼可以看到selectOne其實調用的就是selectList然後,取第一條資料。我們主要分析下selectList

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
           

首先根據配置configuration查詢出具體的mappedstatement.真正的查詢,是通過executor對象來執行,而這裡的Executor也是一個接口對象。

可以看下BaseExcutor裡面的方法;

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++;
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        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;
  }
           

還是要執行到doQuery方法,這裡BaseExecutor裡面是一個abstract方法,真正的實作,需要具體的executor的來實作

看下SimpleExecutor方法

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
           

二級緩存

        mybatis的二級緩存比較靈活,可以使用mybatis自己定義的二級緩存實作,也可以通過實作cache接口,來自定義緩存。也可以使用第三方緩存庫等。        

Mybatis緩存機制概述一級緩存二級緩存

二級緩存主要是在Executor這裡起作用

        二級緩存的開啟

        a)設定全局的cacheEnable為true

        b)在mapper級别配置<cache>(表示一個mapper配置設定一個緩存)或<cache-ref>(多個mapper公用一個cache緩存)

        c)在具體的sql語句上配置useCache="true"

mybatis對二級緩存的實作,都是自己實作了cache接口,它提供了一系列的cache裝飾類,并提供了各種重新整理政策比如FIFO,LRU等

Mybatis緩存機制概述一級緩存二級緩存

參考:

《深入了解mybatis原理》

繼續閱讀