Mybatis源碼分析之(四)mapper訪問數據庫的底層原理(代理方法中具體訪問數據庫的細節)

從之前的文章,我們知道了其實mapper真正執行的方法就下面的最後兩行。(以下所有的分析都基於一次mybatis的一次select查詢,查詢的demo代碼在我的github上地址

MapperProxy類中的invoke函數

  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);
    }
   //獲取MapperMethod 
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //執行查詢並且返回結果
    return mapperMethod.execute(sqlSession, args);
  }

MapperMthod

 private MapperMethod cachedMapperMethod(Method method) {
 //methodCache其實就是一個緩存,將方法與mapperMethod作爲一組鍵值對進行緩存
    MapperMethod mapperMethod = methodCache.get(method);
    //若緩存中沒找到則生成一個,在把生成的mapperMethod加入緩存
    if (mapperMethod == null) {
    //生成mapperMethod的方法
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

這裏解釋一下mapperMethod有什麼作用,首先看一下MapperMethod類的具體參數

public class MapperMethod {

//記錄方法是什麼類型的方法(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;)
  private final SqlCommand command;
  //記錄方法的具體情況(比如說返回一個還是多個,方法返回類型,方法的參數是啥等信息)
  private final MethodSignature method;

//MapperMethod的構造函數
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

}

//執行方法
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //通過command的類型去找實際需要執行的方法,不一個個分析了,只以select的executeForMany爲例子
    //其他都是差不了多少的
    switch (command.getType()) {
    //若是插入類型走這裏
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //若是更新類型走這裏
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //若是刪除類型走這裏
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //若是選擇類型走這裏
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }


//查詢方法
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否需要分頁
    if (method.hasRowBounds()) {
    //拿到rowBounds
      RowBounds rowBounds = method.extractRowBounds(args);
      //查詢
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
    //查詢,進入這條
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }


//實際還是走的有RowBounds 的語句,只是給了默認值,默認值是拿到Integer的最大值
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //從configuration獲取MappedStatement (在初始化的時候就已經放到緩存中了,這裏只是獲取一下)
      MappedStatement ms = configuration.getMappedStatement(statement);
      //通過executor來查詢
      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();
    }
  }

因爲mybatis默認開始Cache,所以我們的Executor是CachingExecutor。但是我們沒在mapper.xml中配置Cache的屬性,所以最終是沒緩存功能的。通過裝飾器模式來增加了Executor的功能

Executor

//這兩個方法是CachingExecutor裏面的
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //生成BoundSql,裏面存放着sql語句
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //生成緩存用的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }


  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //因爲mapper中沒有配置cache,所以這裏的cahce是沒有的
    Cache cache = ms.getCache();
    //cache爲空,所以不會走這裏面的邏輯
    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 and #116
        }
        return list;
      }
    }
    //到來這裏,通過delegate來執行query(因爲CachingExecutor是一個裝飾類,delegate是原始類(在這裏是SimpleExecutor))
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @SuppressWarnings("unchecked")
  @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++;
      //這裏的localCache是一級緩存,是在BaseExecutor中的
      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;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //先從key中放個佔位的值
    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;
  }
  @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();
      //生成一個StatementHandler,對Statement進行處理
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //生成statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //進行訪問
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

//這裏是prepareStatement函數,用來生成訪問數據庫需要的Statement對象
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //如果是debug狀態則對sqlSession進行代理,因爲要打印log
    Connection connection = getConnection(statementLog);
    //
    stmt = handler.prepare(connection, transaction.getTimeout());
    //對參數進行處理
    handler.parameterize(stmt);
    return stmt;
  }

我們進handler的prepare方法看看

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
    //初始化statement 
      statement = instantiateStatement(connection);
      //設置超時
      setStatementTimeout(statement, transactionTimeout);
      //設置fetchSize(mysql不起作用,不支持這個)
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

之後得到了Statement對象後,要去訪問數據庫了。(上面的return handler.query(stmt, resultHandler);)調用的是下面的query方法

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.<E>query(statement, resultHandler);
  }
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  //將Statement強轉成PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    //對數據庫進行查詢(對數據庫進行查詢是jdbc做的事情,mybatis也只是對jdbc進行了包裝)
    ps.execute();
    //對返回結果進行處理
    return resultSetHandler.<E> handleResultSets(ps);
  }

到這裏,我們就進行了一次對數據庫的訪問,並拿到了數據。數據此時在Statement的ResultSet裏面,
如果你熟悉jdbc的話,一定不陌生下面的代碼,我們拿到resultSet後的處理方式如下。

        ResultSet resultSet = preparedStatement.executeQuery();
        while(resultSet.next()) {
            String string = resultSet.getString(1);

        }

由於篇幅問題,mybatis對數據的處理的分析,LZ準備放在下一篇來介紹。

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