淺析mybatis源碼(四)執行sql流程

這篇文章主要從MapperProxy類出發,來縱向分析下Mybatis執行sql的流程。java

MapperProxy類

MapperProxyFactory類只是MapperProxy的工廠類,因此主要看下MapperProxy就行了。MapperProxy是用jdk動態代理來實現對Mapper的代理,所以先看下他的invoke方法。不出意外,這個方法裏就有咱們最關心的代碼,是如何執行的查詢。sql

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);//Object定義的方法,則透傳給Mapper
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);//java8接口的默認方法和靜態方法,代理方法有所不一樣。
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);//先從緩存中取,沒有則根據method新建MapperMethod類
    return mapperMethod.execute(sqlSession, args);//執行sql
  }

能夠看到真正執行Sql的是MapperMethod的execute方法。接下來看下。api

MapperMethod類

MapperMethod的execute方法主要就是判斷sql語句是update,insert,delete仍是select,來執行不一樣的邏輯。update,insert,delete的邏輯基本相同,都是執行sql語句。select較爲複雜,須要將返回結果映射爲java bean。返回多個結果時還要分爲集合,map,Cursor三種狀況。所以,咱們就只看select,而且只考慮將結果集保存在集合的狀況。實如今executeForMany方法中。緩存

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);//獲取參數Map
    if (method.hasRowBounds()) {//邏輯分頁,通常不多用
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {//若結果類型不符合方法的返回類型,則轉化爲對應的結合類型
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

method這個屬性是MethodSignature類,是MapperMethod的內部靜態類。主要封裝了下處理Method的方法。convertArgsToSqlCommandParam這個方法就是當有多個參數時,將此次方法調用的參數和參數名保存在一個map中。session

@Select("select * from product where id > #{id} and price > #{price}")
    ArrayList<Product> findAll(@Param("id") long id, @Param("price") double price);

例如上面這個方法,代理類在執行這個方法時,執行到Object param = method.convertArgsToSqlCommandParam(args)這句時,若入參爲1,20。則param的值爲Map,鍵值對爲:
{
"id":"1",
"price":"20",
"param1":"1",
"price":"1"
}
有了這個Map,則方便將Sql中的#{id},#{price}替換爲"1"和"20"。
command這個屬性是SqlCommand類,是MapperMethod的內部靜態類。主要保存了sql數據,封裝了一些工具方法。 getName方法,則是獲得"${Mapper包路徑}.${Mapper類名}.${執行方法名}",例如,若包名是mapper,上面的方法則是"mapper.ProductMapper.findAll"。這個字符串有什麼用呢?
上一篇說過addMapper的過程當中,主要存儲下了三份數據,其中sql信息存儲在MappedStatement類中,而全部的MappedStatement存儲在mappedStatements這個Map中,而他的key也是這個字符串。
因此此時,咱們能夠找到這個方法對應的MappedStatement。即獲得addMapper時解析Sql的相關數據。 能夠看到最終執行Sql是SqlSession的selectList方法。mybatis默認使用的SqlSession是DefaultSqlSession類,接下來看下。mybatis

DefaultSqlSession類

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);//經過key找到對應的MappedStatement。
      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();
    }
  }

能夠看到,ms即是剛剛說到的MappedStatement。而最終執行查詢的是executor。executor是DefaultSqlSession的一個屬性,是在構造方法中傳來的。DefaultSqlSession是在哪裏構造的呢?就是在第一篇中demo中調用到的,SqlSessionFactory的OpenSession方法。mybatis默認的SqlSessionFactory是DefaultSqlSessionFactory。app

DefaultSqlSessionFactory類

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();//保存了transactionFactory和DataSource
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);//一個session對應一個Executor,一級緩存存在Executor裏,這也就是爲何一級緩存是Session級別的。
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

能夠看到主要分了三步:事務工廠實例化事務對象tx,實例化執行器executor,傳入剛剛的tx,實例化DefaultSqlSession,傳入剛剛的executor。
事務類主要考慮JdbcTransaction,這個類其實就是稍微封裝了下Jdbc的commit,rollback,setAutoCommit等操做。
接下來就到了我們要找的executor了。Executor是個接口,mybatis封裝了三種實現類,BatchExecutor,ReuseExecutor,SimpleExecutor。configuration.newExecutor(tx, execType),這句代碼就是根據類型來實例化不一樣的Executor。以及後續要說的緩存和插件擴展。
繼續回到剛剛的DefaultSqlSession類,裏面主要用到了Executor的query方法。接下來看下Executor吧。工具

Executor

在Executor中,咱們能看到裝飾者模式和模板模式的影子。
BaseExecutor抽象類,實現了query,commit,rollback等基本方法。但doQuery等是抽象方法,由實現類BatchExecutor,ReuseExecutor,SimpleExecutor去具體實現。這種模板模式,耦合代碼較少,值得學習。
CachingExecutor是個裝飾類,爲類賦予緩存功能。不須要爲BatchExecutor,ReuseExecutor,SimpleExecutor寫三個對應的緩存類。
具體的查詢代碼在BatchExecutor,ReuseExecutor,SimpleExecutor的doQuery方法中,咱們就來看下最簡單的SimpleExecutor的doQuery方法吧。學習

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);//實例化StatementHandler
      stmt = prepareStatement(handler, ms.getStatementLog());//獲得jdbc中的 Statement,並進行sql參數綁定、設置超時時間、異常處理等。
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

StatementHandler

StatementHandler的結構也和Executor差很少,也是用了模板模式。SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler這三個實現類分別對應了jdbc的三種Statement,他們的區別能夠查看jdbc文檔。咱們就主要看PreparedStatementHandler。fetch

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;//將statement強制轉換爲jdbc中的PreparedStatement
    ps.execute();//執行sql
    return resultSetHandler.<E> handleResultSets(ps);//將jdbc的ResultSets轉化爲須要的結果類型
  }

能夠看到最終執行的仍是jdbc的PreparedStatement.execute()。而jdbc最終將結果保存在ResultSets中,須要resultSetHandler將其解析爲最終類型。這個過程下篇文章要講,涉及到addMapper過程當中保存的ResultMap。

總結

sql執行的縱向過程以下圖

固然每一個類還有其餘的代碼,之後會對緩存,插件,日誌等進行分析。有的細節也並無去討論,主要是由於這些代碼並不算難,但又比較繁瑣。例如sql參數綁定,已經將參數保存在了map中,剩下就是字符串匹配而後用jdbc的api進行參數綁定。

相關文章
相關標籤/搜索