java源碼學習-Mybatis(3)執行sql過程

前文:Mybatis與數據庫建立連接

jdbc執行流程圖

在這裏插入圖片描述
執行sql的過程就是發起請求的過程, 前文中已經學習瞭如何建立並獲取數據庫連接, 本文主要就是學習一下在service調用mapper獲取結果的過程, 也就是上圖的3~6的過程

方法調用鏈

首先通過上文, 我們知道了Mybatis執行sql必須要先獲取數據庫的連接, 所以我們在SimpleExecutor這個類中的prepareStatement方法中獲取了connection, 我們找到調用prepareStatement的方法, 這個方法應該就是查詢方法doQuery

  @Override
  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();
      // 這裏是獲取一個statement的handler, handler主要是處理生成statement的
      // 在newStatementHandler中還過了一次攔截器鏈
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 使用handler獲取一個statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

至於statement是如何生成的, 我們以後再看

點擊進入query方法

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  	// ps中的內容截圖在下方
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

在這裏插入圖片描述
我們可以看見, 這裏存放的是我們的入參, 以及前面處理好的sql語句, 即實際在mysql中使用的sql語句,
PreparedStatement 的execute方法就是將sql語句送往mysql執行

接下來這個statement通過getResultSet()方法獲取結果集, 這裏用到了代理
在這裏插入圖片描述
這是最後執行的方法

   public ResultSet getResultSet() throws SQLException {
   	// 傳入的statement中有delegate屬性, delegate可以getResultSet
      final ResultSet resultSet = delegate.getResultSet();
      if (resultSet != null) {
         if (proxyResultSet == null || ((ProxyResultSet) proxyResultSet).delegate != resultSet) {
            proxyResultSet = ProxyFactory.getProxyResultSet(connection, this, resultSet);
         }
      }
      else {
         proxyResultSet = null;
      }
      return proxyResultSet;
   }

這一步的return中會處理上面獲得的結果集

  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }

封裝結果集

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    // 獲取元數據
    final ResultSetMetaData metaData = rs.getMetaData();
    // 獲取查詢出來的字段個數
    final int columnCount = metaData.getColumnCount();
    // 循環設置字段名以及mysql的變量類型和java的類型映射
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }

結果集返回給resultSetHandler
在這裏插入圖片描述

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 這裏接收上面的結果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    // 處理指定個數的結果集
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 進入DefaultResultSetHandler中的結果集處理
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

DefaultResultSetHandler中的結果集處理, 具體操作不在本文描述, 以後再講
通過這個方法處理結果集, 會返回我們需要的java類型, 並且將字段的值注入
handleRowValues這個方法中會處理字段和值的映射

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      // 對應於jdbc中的關閉結果集操作
      closeResultSet(rsw.getResultSet());
    }
  }

結果集關閉後就層層返回到doQuery關閉statement, 由於我們的結果設置的不是list也不是map, 所以使用的是DefaultSqlSession中的selectOne方法

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    // 可以看見無論結果設置是否多個, 都是調用selectList去數據庫中查詢
    List<T> list = this.selectList(statement, parameter);
    // 由於是selectOne, 所以這裏返回的list必須是只有一個元素
    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;
    }
  }

最後的結果返回SqlSessionTemplate類, 這個攔截器是template的內部類,
是在MapperMethod類中的execute方法的selectOne進入的方法,
大家去看看Template類可以發現, selectOne返回的是this.sqlSessionProxy.selectOne,
而這個sqlSessionProxy屬性是構造器中new出來的內部類SqlSessionInterceptor
也就是最後進入的其實是下面這個invoke方法

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
      	// 這裏這個invoke進入的是DefaultSqlSession中的selectOne方法
      	// 真正被執行的selectOne
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
       	  // 最後關閉會話
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

我們調用自己寫的mapper中的方法之後, 會通過代理進入MapperProxy中, 可以看見這裏調用了上面的execute, 這個invoke返回之後, 就會在我們的service得到最終的java對象

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

後記

本篇主要是講了講Mybatis在執行sql的時候, 得到數據庫連接後, 何時創建statement, 何時創建resultSet
不難發現, mybatis的很多實現都是依賴代理, 我們調用mapper方法後就會進入MapperProxy, 這裏就是一個代理
如果除去所有代理的話, 其實流程就是先判斷返回的結果集個數從而決定調用selectOne還是selectList方法, 然後判斷一級緩存, 如果不存在就去數據庫查找, 查找過程就是先通過Hikari中獲取的connection創建statement, 然後提交sql到數據庫, 獲得結果後通過resultSetHandler處理結果返回到我們調用mapper方法的service中

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