• 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
    
        
        	
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    所以 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

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

    @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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