mybatis源码分析之05一级缓存

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码分析之05一级缓存相关的知识,希望对你有一定的参考价值。

首先需要明白,mybatis的一级缓存就是指SqlSession缓存,Map缓存!

通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlSessionFactory创建的,下面是源码

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 

重点关注标红的两行代码,虽然mybatis的一级缓存就是SqlSession缓存,但若是说得具体点应该是SqlSession中的executor缓存。所以,非常有必须去了解一下这个executor对象。

由上图知,Executor有好几个实现 , 这种情况下,只能debug来一探究竟了!

在额外说一点,mybatis提供了三种类型的Executor

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}
SIMPLE: 最基本的
REUSE: 重复使用
BATCH: 批量操作

 

   题外话: 责任链在工作中非常常见,但是如何建立链中executor之间前后关系,套路各有不同,mybatis框架中的这种套路值得借鉴

由上面的debug知,executor是真实类型是CachingExecutor, 而这个CachingExecutor构造器中又是SimpleExecutor(我只能说套路太多)。

所以,DefaultSqlSession中的持有的Executor就是CachingExecutor。

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();
  @Override
  void close();
  void clearCache();
  Configuration getConfiguration();
  <T> T getMapper(Class<T> type);
  Connection getConnection();
}

所以,上面的SqlSession接口中这一批操作数据库的方法,真正去执行的executor是CacheExecutor, 下面看下CacheExecutor中query方法(select操作)源码
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
                throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

哈哈,CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); 这行代码一看就是知道 与缓存脱不了关系了,事实上他就是创建缓存的key

具体代码:
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

这里的delegate又是什么鬼? 哈哈,前面说了CacheExecutor的构造中传了一个SimpleExecutor ,而这个delegate 就是SimleExecutor。

所以,要看mybatis是如何创建这个缓存key还得去SimleExecutor类中一探真相。而我们去到SimleExecutor类中,发现根本就没有createCacheKey()方法嘛!

 

 好嘛,真没有, 但是不要忘了java的特性,儿子没有找老子,在BaseExecutor很容易 就看到这个createCacheKey()方法。

 @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

看起来挺复杂的,不过我们不用过多关心。再复杂不也是创建一个key嘛!生成好缓存key后,我们接着往下看

 

mybatis中虽然默认开启了二级缓存,但是我们并没有配置二级缓存,所以程序并不会进入到红框部分,

直接走delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

 

******************************************************

delegate: 真实类型SimpleExecutor

key: 就是前面生成的CacheKey对象

******************************************************

SimpleExecutor 中并没有query()方法,同样会找到它的父类BaseExecutor, 在这儿就会看到mybatis先是从本地缓存中取值 ,如果缓存中有值就返回该值 ,如果没有再查库,

源码如下:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
               CacheKey key, BoundSql boundSql) throws SQLException { // 查询结果 List<E> list; try { // 根据生成缓存key从localCache中取值 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 如果缓存中查询为null,则从数据库中查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } return list; }

 

接着再看queryFromDatabase()方法

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                        CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 1. 先往缓存localCache put一个EXECUTION_PLACEHOLDER,这是啥鬼哟??? localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 2. 查库,具体逻辑由子类实现,即SimpleExecutor list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 3. 又将key从 localCache 中移除了, 这是为啥呀??? localCache.removeObject(key); } //4. 将查库结果又put到localCache中 localCache.putObject(key, list); return list; }

 

mybatis框架就是这样使用一级缓存的,下面再来看看这个localCache到底是啥鬼?

 protected PerpetualCache localCache;

就是这个对象!!!

public class PerpetualCache implements Cache {
  private Map<Object, Object> cache = new HashMap<>();

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

嗯, 由上面的源码可以看出来,真正是缓存在PerpetualCache 类中的HashMap中的。

 

*****************************************************************************************

这里有点小技巧呀,mybatis单独将这个缓存Map抽离成一个类PerpetualCache,

如果换成我们呢,大概是直接在BaseExecutor类中写个成员Map吧!!!

******************************************************************************************

 

BaseExecutor类中的update()方法

  public int update(MappedStatement ms, Object parameter) throws SQLException {
    // 清理localCache缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

 

别问insert,delete方法是怎么处理缓存的,看源码就知道 ,这两种方法在Executor层面都转换成了update操作!

 

最后,由于mybatis一级缓存是jvm级别的,所以,在集群情况下,可能会出现数据不同步的情况,这时可以使用SqlSession#clearCache()清除缓存,每次查询都查库;

而且,由上面的源码分析也可以知道 ,如果有配置二级缓存,一级缓存就会失效,而我们完全可以使用redis来做二级缓存,这样就完美的避免了使用一级缓存数据不同步的问题!

 

 

 

以上是关于mybatis源码分析之05一级缓存的主要内容,如果未能解决你的问题,请参考以下文章

mybatis关于二级缓存的配置及源码分析

mybatis的缓存机制源码分析之二级缓存解析

MyBatis源码分析:MyBatis Cache分析

Mybatis缓存的使用和源码分析

通过源码分析MyBatis的缓存

MyBatis源码分析五MyBatis的缓存