mybatis隨筆

SqlSession

SqlSession提供執行Sql命令,獲取Mapper以及事務管理的接口。
內部有selectOne,selectList,selectMap,insert,update,commit,rollback,getConnection等方法。

selectList

作爲select操作的最主要的實現,selectOneselectMap都是通過對selectList的結果進行處理得到。
例如selectOne

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

  @Override
  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.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;
    }
  }

selectMap

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
            configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }

selectList通過Executor執行查詢操作

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

query以BaseExecutor爲例,首先從localCache中獲取結果protected PerpetualCache localCache;這個cache是基於HashMap實現的。如果緩存中存在就直接返回結果,否則調用queryFromDatabase()從數據庫中查詢,此方法請看下面。在完成結果的查詢後,會根據緩存的範圍LocalCacheScope判斷是否需要清空緩存,如果是STATEMENT級別,則會清空緩存。

 @Override
  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();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

queryFromDatabase委託給doQuery實現查詢。在查詢完成後會保存到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;
  }

doQuery看到這裏就已經很熟悉了,出現了JDBC編程中常見的Statement接口和數據庫連接Connection。看到有個StatementHandler,這裏newStatementHandler()返回的是RoutingStatementHandler在這裏插入圖片描述
RoutingStatementHandler本質上是代理類,根據StatementType這個枚舉類型來返回真正處理的Handler
在這裏插入圖片描述

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

我們選到PreparedStatementHandlerquery就是純JDBC的操作了。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

update

在mybatis中insertdelete是基於update實現的

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

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
  
  @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  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();
    }
  }

來到BaseExecutor關於update的實現,會發現在更新之前會清空緩存

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

一級緩存

一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話(SqlSession)層面進行緩存的。MyBatis 的一級緩存是默認開啓的,不需要任何的配置。首先我們必須去弄清楚一個問題,在MyBatis 執行的流程裏面,涉及到這麼多的對象,那麼緩存PerpetualCache 應該放在哪個對象裏面去維護?如果要在同一個會話裏面共享一級緩存,這個對象肯定是在SqlSession 裏面創建的,作爲SqlSession 的一個屬性。

DefaultSqlSession 裏面只有兩個屬性,Configuration 是全局的,所以緩存只可能放在Executor 裏面維護——SimpleExecutor/ReuseExecutor/BatchExecutor 的父類BaseExecutor 的構造函數中持有了PerpetualCache。在同一個會話裏面,多次執行相同的SQL 語句,會直接從內存取到緩存的結果,不會再發送SQL 到數據庫。但是不同的會話裏面,即使執行的SQL 一模一樣(通過一個Mapper 的同一個方法的相同參數調用),也不能使用到一級緩存。

每當我們使用MyBatis開啓一次和數據庫的會話,MyBatis會創建出一個SqlSession對象表示一次數據庫會話。

在對數據庫的一次會話中,我們有可能會反覆地執行完全相同的查詢語句,如果不採取一些措施的話,每一次查詢都會查詢一次數據庫,而我們在極短的時間內做了完全相同的查詢,那麼它們的結果極有可能完全相同,由於查詢一次數據庫的代價很大,這有可能造成很大的資源浪費。

爲了解決這一問題,減少資源的浪費,MyBatis會在表示會話的SqlSession對象中建立一個簡單的緩存,將每次查詢到的結果結果緩存起來,當下次查詢的時候,如果判斷先前有個完全一樣的查詢,會直接從緩存中直接將結果取出,返回給用戶,不需要再進行一次數據庫查詢了。

二級緩存

一級緩存只是對於一個Session有效,那麼二級緩存爲了使更大範圍有效採用CachingExecutor和在MappedStatement中設置緩存來實現mapper範圍內的緩存。如何利用到這個二級緩存呢?如下分析select的過程

CachingExecutor的query()會發現它會從MappedStatement中獲取cache。如果存在對應的結果就直接返回,否則調用BaseExecutor的query進行查詢。因此,在開啓Mybatis二級緩存的情況下,查詢過程是MappedStatement中Cache -> BaseExecutor的localCache -> queryFromDatabase()

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

在update的情況下會清空緩存

 @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  
private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);
    }
  }

MappedStatement

對應與具體在Mapper.xml中寫的方法,如下圖
在這裏插入圖片描述
這個MappedStatement中屬性id就是在這裏被賦值的。而Configuration中有個mappedStatements Map就是存放各種方法。Key爲id。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章