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中獲取。由於這個緣由,咱們須要在寫代碼時注意到,在第一次查詢後獲得結果集,若是結果集這時候咱們人爲修改了,那麼就會在第二次查詢時會獲取到咱們修改後的結果集,而不是正確的數據。

相關文章
相關標籤/搜索