Mybatis一级缓存源码详解

解析基于Mybatis 3.4.4 Release,近几个版本应该都适用。

Mybatis一级缓存默认是开启的,缓存数据时存储在Executor中,而Executor则被SqlSession持有。

我们先来看生成SqlSession和Executor实例的过程:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    //通过连接池生成SqlSession对象
    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);
      //生成执行器Executor
      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();
    }
  }

}


public class Configuration {

    // 若未显式指定,defaultExecutorType 默认值为 ExecutorType.SIMPLE
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;    
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
        // 二级缓存也是默认开启的,这里下篇博客再讲
        if (cacheEnabled) {
            executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

}

ReuseExecutor,SimpleExecutor 都直接继承自BaseExecutor,他们在实例化时都会调用BaseExecutor的唯一构造方法:

public abstract class BaseExecutor implements Executor {

  ......
  protected Transaction transaction;
  protected Executor wrapper;

  protected PerpetualCache localCache;
  protected Configuration configuration;


  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue();
    // 初始化localCache 属性,生成一个PerpetualCache对象
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

}

public class PerpetualCache implements Cache {

  private String id;

  private Map cache = new HashMap();

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

  ......

}

PerpetualCache 持有一个HashMap类型的属性,可作为一个缓存实现。

接下来我们看DefaultSqlSession在增删改查时的缓存操作。

public class DefaultSqlSession implements SqlSession {

    public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            // 查询操作借助executor对象来执行query实现
            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();
        }
    }
}


public abstract class BaseExecutor implements Executor {

    public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 创建一个CacheKey对象,用于缓存的Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    // 创建CacheKey,将查询时的参数,SQL字符串,分页数据等都填充进去
    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 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;
    }

}

CacheKey 创建时会关联Mapper的id,也就是方法名称,Offset,Limit,SQL语句和SQL的参数。

CacheKey 重写了equals和hashCode方法,当两个CacheKey 这些数据完全相同时,就能 以 后一个CacheKey 为Key获取到前一个CacheKey 存入的Value。

public abstract class BaseExecutor implements Executor {

     public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ......
        List list;
        try {
          queryStack++;
          // 查询前先勇CacheKey 从localCache获取Value,如果有值就不用再查数据库了
          list = resultHandler == null ? (List) localCache.getObject(key) : null;
          if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        ......
    return list;
  }

}

当DefaultSqlSession Insert,Update,Delete时,会调用Eexcutor的Update方法:

public abstract class BaseExecutor implements Executor {

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

    public void clearLocalCache() {
        if (!closed) {
          // 清空Cache
          localCache.clear();
          localOutputParameterCache.clear();
        }
    }

}

当DefaultSqlSession Commit或者Rollback时,会调用Executor相应的Commit和Rollback方法:

public abstract class BaseExecutor implements Executor {

    @Override
    public void commit(boolean required) throws SQLException {
        if (closed) {
            throw new ExecutorException("Cannot commit, transaction is already closed");
        }
        clearLocalCache();   // 清空Cache
        flushStatements();
        if (required) {
            transaction.commit();
        }
    }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();   // 清空Cache
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

}

他们都会清空缓存。

总结:

  1. Mybatis一级缓存默认开启,缓存数据存储在Executor中,实际就是BaseExecutor对象的localCache属性,属性类型是PerpetualCache。
  2. 缓存的Key类型是CacheKey,Mybatis重写了其hashcode和equals方法,需要Mapper方法名称一致,SQL字符串一致,Limit,Offset一致,SQL参数一致才能在Map中有相同的映射关系。也就是两个CacheKey的这些数据相同,后一个CacheKey才能获取到前一个存入的Value。
  3. Insert,Update,Delete操作会清空缓存
  4. Mybatis的SqlSession在Commit和Rollback时会清空缓存。如果当前数据库操作不在事务中,那么每个操作完成后都是默认提交或者回滚的,都会清空缓存,那么缓存就没有效果了。
  5. 一级缓存的使用前提很苛刻,首先需要在同一个事务中,且多次查询操作完全相同,且多次操作之间不能夹杂 增,删,改 操作。

你可能感兴趣的:(mybatis)