天天看點

【Mybatis源碼解析第五章】Mybatis是如何執行sql的

緣起

在上一章中,我們講述了Mybatis是如何解析它的配置檔案的。在解析的過程中,我們得到了一個至關重要的對象:Configuration對象,那麼我們的sql又是在什麼時候執行的呢?如何執行的呢?讓我們來一起探究​

​selectOne()​

​背後的故事

Test test = sqlSession.selectOne("TestMapper.selectTest", 1);      

​selectOne()​

​​其實是調用的​

​selectList()​

​然後判斷,如果結果集大于一條,就報錯:

Expected one result (or null) to be returned by selectOne(), but found:XXX      

關鍵代碼如下

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

​selectLis​

​方法如下

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

​configuration.getMappedStatement(statement);​

​​就是前往​

​Configuration​

​​對象的​

​mappedStatements​

​​屬性中擷取到​

​MappedStatement​

​​,我們知道​

​MappedStatement​

​​儲存着我們的​

​sql​

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
  if (validateIncompleteStatements) {
    buildAllStatements();
  }
  return mappedStatements.get(id);
}      

拿到了我們的​

​MappedStatement​

​之後,想必就是去取出對應的​

​sql​

​然後執行了。我們看​

​executor.query()​

​方法

該方法最終會調用到​

​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);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}      

上訴​

​prepareStatement​

​​方法最終會調用到​

​instantiateStatement​

​方法,該方法代碼如下

protected Statement instantiateStatement(Connection connection) throws SQLException {
  String sql = boundSql.getSql();
  if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    String[] keyColumnNames = mappedStatement.getKeyColumns();
    if (keyColumnNames == null) {
      return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    } else {
      return connection.prepareStatement(sql, keyColumnNames);
    }
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}      

看到了吧?從對應的​

​MapperStatement​

​對象中取出​

​BoundSql​

​對象,然後取出​

​BoundSql​

​中的​

​sql​

​,然後使用與​

​JDBC​

​中相同的方式封裝成一個​

​Statement​

​對象。

還記得​

​JDBC​

​中的代碼吧?​

​JDBC​

​關鍵代碼

PreparedStatement preparedStatement = connection.prepareStatement("select * from test where id = ?");
preparedStatement.setString(1,"1");
ResultSet resultSet = preparedStatement.executeQuery();      

這個​

​connection.prepareStatement​

​是不是與​

​Mybatis​

​源碼中的一模一樣呢?

我們再回到前面的​

​doQuery​

​方法,其中有一句代碼​

​handler.query(stmt, resultHandler);​

這個query方法的代碼如下

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

想必你已經看到了那個​

​execute()​

​了,現在明白了吧?

總結

當我們執行​

​sqlSession.selectOne()​

​​這句代碼時,​

​Mybatis​

​​會去解析配置檔案的時候就建構好的​

​Configuration​

​​對象中的​

​mapperStatements​

​​屬性中通過我們傳入的id取到對應的​

​MapperStatement​

​​對象,然後從​

​MapperStatement​

​​對象中取出​

​BoundSql​

​​對象,然後取出​

​BoundSql​

​中的SQL語句,最後使用JDBC的方式執行。