天天看点

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原理》

继续阅读