MyBatis的localCache问题及解决方案

背景

在单元测试中,我们需要验证不同的场景下系统功能是否正常。而构造不同的测试场景,就需要对db中的数据做相应的订正,同时通过事务的回滚,保证单元测试结束后,db中的数据回归到初始状态

问题描述

单元测试类上已经申明了@Transactional注解,在下面的这个测试场景代码中

result1 = queryResult();
jdbcTemplate.update("***");
jdbcTemplate.update("***");
result2 = queryResult();

result1和result2的结果完全一致,不符合预期

原因探讨

mybatis默认开启了localCache功能,默认的scope是session级别。那么在同一个session中,相同的Query重复执行的时候,直接从缓存中读取结果。

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ……
    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--;
    }
    ……
    return list;
  }

解决方案

只需要在select element中加上flushCache=”true”,则这个statement执行结束后,会将localCache清空,下一次查询的时候就会直接查询数据库

    <select id="selectAll" resultMap="BaseResultMap" flushCache="true">
        select
        <include refid="Base_Column_List"/>
        from Table
    </select>

如果配置了这个选项,则在执行之前会将缓存清空,保证从数据库重新查询一次

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      ……
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

不过需要注意的是,mybatis默认的作用域是session级别,也就是说清空以后,会影响这个session内的所有sql。
可以在mybatis的配置中,配置cache的作用域是session级别还是sql级别

<setting name="localCacheScope" value="SESSION"/>
<setting name="localCacheScope" value="STATEMENT"/>

发表评论