MYBATIS事务内的查询缓存

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

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

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方法apache

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

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

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中,并返回结果。ui

        从上面能够看出,若是两次调用同一条查询语句而且查询条件一致,那么就只会查询一次,第二次会从localCache中获取。那么若是第一次查询后有insert、update、delete、操做时,第二次相同查询还会从localCache中获取吗?this

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

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中获取。由于这个缘由,咱们须要在写代码时注意到,在第一次查询后获得结果集,若是结果集这时候咱们人为修改了,那么就会在第二次查询时会获取到咱们修改后的结果集,而不是正确的数据。

相关文章
相关标签/搜索