MYBATIS事务内的查询缓存

    mybatis的查询使用的sqlSession类主要为:DefaultSqlSession.java,在这个类里提供了selectOne,selectList,insert,update,delete,select,selectMap之类的dml通用方法以及commit、rollback这类的事务控制方法。

        这里今天主要讲述dml方法涉及到对应的事务内缓存,首先我们先看看selectOne方法

 public <T> T selectOne(String statement) {
    return this.<T>selectOne(statement, null);
  }

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

从上可以看出selectOne最终都是调用的selectList,接下来看selectList

  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

selectList方法最终是要调用到 selectList(String statement, Object parameter, RowBounds rowBounds) 方法。在这里它会调用 org.apache.ibatis.executor.Executor 类的实现类的 query方法进行查询相关的后续逻辑。这里先看看对应query方法

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

  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);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

在这里可以看到会根据sql、sql对应的参数创建一个CacheKey。第一个query方法会调用第二个,然后先会从MappedStatement中获取cache,这里先不关注这个,因为只有设置了useCache的时候才有可能会有cache,而本次讨论查询缓存不是这个cache。接下来就要调用BaseExecutor.query方法

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    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--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

上面这一行是从localcache中获取事务中的查询缓存,如果有就返回对应的缓存,没有则会去数据库里查询,这时就进入了queryFromDatabase方法了

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

从这里可以看出会将查询结果放入localCache中,并返回结果。

        从上面可以看出,如果两次调用同一条查询语句并且查询条件一致,那么就只会查询一次,第二次会从localCache中获取。那么如果第一次查询后有insert、update、delete、操作时,第二次相同查询还会从localCache中获取吗?

        我们再来看看DefaultSqlSession类中的insert、update、delete方法


  public int insert(String statement) {
    return insert(statement, null);
  }

  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  public int update(String statement) {
    return update(statement, null);
  }

  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  public int delete(String statement) {
    return update(statement, null);
  }

  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

可以看出来最终都是调用的 update(String statement, Object parameter) 方法,这样很好,我们就只要关注这个方法的执行了。从configuration中获取对应mybatis以及我们自己要调用的dml方法的相关配置后,就会执行executor.update 方法。现在,我们进入这个方法看看

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

从这可以看出来这里会调用clearLocalCache()方法后,再执行doUpdate方法去更新数据。我们来看看clearLocalCache()方法到底干了些什么?

  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

很简单,就是清理localCache中的缓存。也就是即使我们在同一个事务中使用了两次完全相同的查询,如果在这两次查询中有update、delete、insert操作,那么第二次查询会直接查询数据库,而不会从localCache中获取,而如果没有,那么就会从localCache中获取。因为这个原因,我们需要在写代码时注意到,在第一次查询后得到结果集,如果结果集这时候我们人为修改了,那么就会在第二次查询时会获取到我们修改后的结果集,而不是正确的数据。

转载于:https://my.oschina.net/u/615618/blog/1579196

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值