MyBatis 缓存

MyBatis 缓存

MyBatis 是现在国内比较流行的 ORM 框架,在学习 MyBatis 的时候,不得不了解 MyBatis 的两级缓存,要了解 MyBatis 的缓存,先要了解 MyBatis 几个重要的对象

  • SqlSession - 对应的一次数据库会话,由 SqlSessionFactory 的 openSession 创建,一次会话并不代表只能执行一条 SQL
  • MappedStatement - 存储了 SQL 对应的所有信息,XMLStatementBuilder 解析 XML 或者注解的时候,由 parseStatementNode 方法生成,放入到 configuration 中保存
  • Executor - 真正对数据库操作的对象,由 Configuration 的 newExecutor 创建
  • namespace - 用来区分 sql 命令,和 statementid 一起生成的 key 值作为 sql 的唯一标识

MyBatis 一级缓存

首先,一级缓存的配置有两种

  • SESSION(默认)
  • STATEMENT

    
    	
    

所以 MyBatis 的一级缓存可以是 SqlSession 级别的,也可以是 Statement 级别的


原理

当客户端执行 SQL 的时候,会将查询结果封装到 SqlSession 的 Executor(BaseExecutor) 中的 localCache 属性中(Executor 的 query 方法),其底层是一个 HashMap

protected PerpetualCache localCache;

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

key 值为 MappedStatementId + Offset + Limit + SQL + SQL 中的参数一起构成 CacheKey(Executor 的 createCacheKey 方法),生成 Key 的方法

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

作为在市场叱咤了这么多年的框架,当然会考虑在数据更新之后查到缓存的问题,所以在更新数据的时候会将缓存清除(此处是无差别攻击)

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

如果想跳过一级缓存,可以配置