天天看點

Mybatis中擷取擷取代理對象的之後的執行操作Mybatis中擷取擷取代理對象的之後的執行操作

Mybatis中擷取擷取代理對象的之後的執行操作

之前已經說了Mybatis中mapper是怎麼和XMl關聯起來的已經分析了,Mybaits從XML是怎麼和Mapper對應上來的。這裡從宏觀上介紹一下是怎麼執行的。之後會在具體的部落格裡面介紹。

1. 幾個重要的步驟

  1. 調用方法的時候調用

    MapperProxy

    invoke

    方法,并且建構

    MapperMethodInvoker

    調用

    invoke

    方法,然後會調用

    MapperMethod

    方法的

    execute

    方法。
  2. 組裝參數,将方法的參數組成Map,Map的key是String,Value是具體的參數值(ParamNameResolver)
  3. 調用sqlSession的select方法
  4. 通過StatementId(全限定類名+方法名)從Configuration裡面解析

    MappedStatement

    從下面開始,都是在Executor裡面運作的。
  5. 通過指定器(Executor)執行

    query

    方法,在這裡面建構查詢所需要的一切操作。并且執行查詢操作,将查詢的結果封裝實體類,傳回
    1. 建構BoundSql對象(通過參數和原始的sql來解析動态sql,生成用于PrepareStatement裡面的sql)。
      select * from t_class where id = ? and name in ?
                 
    2. 建構

      CacheKey

      (用作緩存,通過sql,同樣的參數,同樣的offset或者limit)
    3. 先從二級緩存中查找(如果指定開啟)
    4. 再從一級緩存中查找(這肯定是開啟的)
    5. 建立

      StatementHandler

      在建立它的時候。會建立

      ParameterHandler

      ResultSetHandler

      ,并且調用插件方法包裝。
    6. 調用

      StatementHandler

      prepare

      方法
      1. 将之前

        BoundSql

        對象解析好sql設定給Connection

        (connection.prepareStatement(sql))

      2. 給查詢設定設定逾時時間和

        fetchSize

      3. 調用

        ParameterHandler

        來給設定此次查詢的參數。
      4. 執行查詢。
      5. 調用

        ResultSetHandler

        來處理結果映射
    7. 傳回結果,并且将結果放在一級緩存裡面。
    8. 如果條件滿足,也放在二級緩存裡面。

    那麼下面就簡單的來看一下大體的流程吧。之後會圍繞具體的方面來分析,流程圖就不畫了,上面的文字寫的也很清楚了。

    測試案例

    @BeforeAll
      static void setup() throws Exception {
        //這裡隻是為了測試友善,運作了測試的腳本。這裡的dataSource和MapperConfig裡面的沒有關系。
    
        createStudentDataSource(); //這就是擷取連接配接,執行腳本。
        final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
        final Reader reader = Resources.getResourceAsReader(resource);
        // 加載配置檔案,解析mapper,mapper裡面的每個方法都對應一個MapperStatement。大體就是這樣,但是具體的實作就複雜了,比如動态sql,mybatis的配置資訊。
        //所有的資訊都關聯到 SqlSessionFactoryBuilder裡面的Configuration實體類。包括mapper,setting,還有代理對象的建立
      sqlMapper = new SqlSessionFactoryBuilder().build(reader);
      }
    
    
    @Test
      public void testShouldSelectClassUsingMapperClass(){
        try(
          SqlSession session = sqlMapper.openSession()
          ){
          ClassMapper mapper = session.getMapper(ClassMapper.class);
          long start = System.currentTimeMillis();
          System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"}));
          System.out.println("start:" + (System.currentTimeMillis()-start));
    
    
          long start1 = System.currentTimeMillis();
          System.out.println(mapper.listClassOwnerStudentByClassId(2,new String[]{"狗蛋"}));
          System.out.println("start1:" + (System.currentTimeMillis()-start1));
    
    
    
          long start2 = System.currentTimeMillis();
          System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"}));
          System.out.println("start2:" + (System.currentTimeMillis()-start2));
    
          session.commit();
        }
      }
               

調用MethodProxy,調用Invoke方法,并且組裝參數

// MapperProxy的Invoke方法,這裡會建構MapperMethodInvoker大多數的是PlainMethodInvoker。但是對于default修飾的方法
//是 DefaultMethodInvoker,

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//如果是object類裡面的方法,直接調用就好了
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }



//MapperMethod,通過之前建構的時候确認的類型來判斷,這裡的模式美其名曰,政策模式
// 這裡我用SELECT方法舉例,
// 在這個分支裡面會根據傳回值的類型不同,來約定傳回方法分支。
// 下面的代碼是按照method.returnsMany()來的。
 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result; //美其名曰,政策模式
    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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
 }

           

組裝參數,将方法的參數組成Map,Map的key是String,Value是具體的參數值(ParamNameResolver)

在組裝MapperMethod的時候會建構

MethodSignature

,這裡面會設定

ParamNameResolver

,在從上面的代碼往下走的時候,會通過

ParamNameResolver

來解析參數,即就是組裝成map。

// ParamNameResolver中的getNamedParams方法
// 這裡的方法能解釋在Mapper裡面可以用Mybatis提供的預設參數。
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) { //這裡就可以看到,會給參數設定一個預設的值,如果不設定的話,并且還會生成按照下标會同時會生成GENERIC_NAME_PREFIX字首,同時也會放在map裡面傳回
      Object value = args[names.firstKey()]; //這裡如果隻有一個參數,并且它還沒有标注param注解,下面的這個方法,就很簡單,如果是collection或者array的話,存放預設值,如果不是,就把原來的參數值直接傳回
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }
           

通過StatementId(全限定類名+方法名)從Configuration裡面解析

MappedStatement

// DefaultSqlSession中的selectList。
 // 從configuration的MapperStatement通過StatementId擷取MapperStatement。
  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try { //拿到mapperStatement,這個是在xml中解析mapper檔案的時候解析出來的
      MappedStatement ms = configuration.getMappedStatement(statement);
      
      // 查詢交給Executor來執行。
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);// 這裡就很離譜,居然還再次包裝了一下下。很離譜,對應Collection或者Array
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
           

建構BoundSql對象(通過參數和原始的sql來解析動态sql,生成用于PrepareStatement裡面的sql)。

如果開啟了二級緩存

DefaultSqlSession

中的

Executor

CachingExecutor

,如果沒有開啟就是直接是

SimpleExecutor

// ExecutorType有三種,SIMPLE,REUSE,BATCH,一般來說,都是simple。并且它預設的執行器的類型就是simple
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }

    //如果開啟了緩存就用CachingExecutor來包裝之前搞好的Executor,
    // CachingExecutor裡面有一個TransactionalCacheManager,這是mybatis實作二級緩存的重點。
    
    
    // cacheEnabled這個辨別位。就是xml中Setting設定的。,預設是開啟,如果開啟,就将executor用CachingExecutor包裝。
    // 在說一下,executorType預設是SIMPLE,并且這個也是可以通過xml中setting的defaultExecutorType來設定的。
   
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //在interceptorChain應用Executor。
    //waring 這裡也很重要,這是是實作mybatis攔截器的主要實作。主要看這個方法,Interceptor類裡面的wrap方法
    // 其實這裡就是生成了代理對象,調用 Plugin.wrap(target, this);來生成代理對象,在這裡會解析interceptor的上面的注解,組成map,key是接口的class。
    // value是set,裡面存放的需要攔截的方法。如果需要生成代理對象就生成代理對象,InvocationHandler是Plugin。 具體的還得看看方法裡面是怎麼寫的。
    executor = (Executor) interceptorChain.pluginAll(executor);

    return executor;
  }
           

通過executor來執行方法,在這個方法裡面會通過參數來解析動态sql。并且将sql中參數的位置變為

?

占位符。

//MappedStatement的 getBoundSql方法,在這裡會将之前搞好的參數(也就是那個Map)傳遞進來并且根據這些參數,來解析動态sql。
//動态sql的具體實作是 SqlNode。接口
public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }
           

建構

CacheKey

(用作緩存,通過sql,同樣的參數,同樣的offset或者limit)

建構緩存key,建構這樣的key的要求可多了,從代碼就可以看出,一個cachekey方方面面那是得完全一樣的呀。包括,參數,原始sql,包括sql參數的位置。我意思是比如有這樣的一個sql

select * from t_class where a = 1 and b = 2 
select * from t_class where a = 2 and b = 1 
           

這兩sql對于我們來說,結果是一樣的,但對于Mybatis來說,這可不一樣,不能共用一個CacheKey。

話說回來,這确實的一模一樣,但凡要是有一點不一樣,兩次查詢,隻因為那一點不一樣,出來的結果确實一樣,那不離譜了嗎?

// BaseExecutor的createCacheKey方法
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//方法參數的映射
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();//在這裡肯定是會用到類型處理的,
    // mimic DefaultParameterHandler logic  這個parameterMappings,按照下标和?對應。下标為0的,表示為sql中第一個問号。
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();//參數名字。
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    } //上面的都是通過propertyName來擷取value。
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

           

先從二級緩存中查找(如果指定開啟),再從一級查詢,并且将結果放在二級和一級緩存中去。

二級緩存相關代碼

// CachingExecutor中的query方法

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {

    // 這個緩存是在mapper.xml中配置的。在解析xml檔案的時候配置的。
    Cache cache = ms.getCache();

    if (cache != null) {
      flushCacheIfRequired(ms);
      // MapperStatement如果要配置緩存,并且resultHandler為null。就去查二級緩存。
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        
         //從二級緩存中查詢,如果沒有再從一級緩存裡面去查,這裡的delegate就是BaseExecutor
        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);
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

           

一級緩存相關代碼

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) {//lcnote 這個deferredLoads是幹嘛的,
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //如果緩存緩存的作用域是STATEMENT,在查詢結束之後會清楚緩存,這肯定是線程不安全的。有問題,如果兩個線程同時操作,一個不需要清楚緩存
      // 一個需要清楚緩存,那不就得麼的了。出現問題了?
      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 {
      // 移除掉之前用于占位的key
     // 這裡的remove我不是很了解,與其說是占位,name在這裡直接put覆寫就好了,這裡這樣做怕是為了防止doQuery發生異常,導緻這個緩存是錯誤的,是以, 這裡必須有finally,這都是細節細節
      localCache.removeObject(key); 
    }
    
    
    localCache.putObject(key, list);//放在一級緩存中。

    
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

           

建立

StatementHandler

在建立它的時候。會建立

ParameterHandler

ResultSetHandler

,并且調用插件方法包裝

建立StatementHandler,并且用interceptor包裝他,關于interceptor在前面的部落格中已經介紹了。

//Configuration的newStatementHandler方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
           

建立parameterHandler和resultSetHandler并且interceptor包裝

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
           

得到Connection并且設定sql,和設定此次查詢的一些基本屬性

// SimpleExecutor的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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);//查詢并且映射結果集
    } finally {
      closeStatement(stmt);
    }
  }
           

設定sql,設定屬性

//BaseStatementHandler的prepare方法 
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //執行個體化和初始化,這裡會調用Connection的 connection.prepareStatement(sql)方法,設定原始的sql
      statement = instantiateStatement(connection);
      //設定statement屬性
      setStatementTimeout(statement, transactionTimeout);
      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);
    }
  }
           

調用

ParameterHandler

來給設定此次查詢的參數。

// DefaultParameterHandler的方法,
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
   //得到參數的映射 ,這裡會解析參數的類型,将實際參數的類型轉變為JDBC認可的類型。
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            
            //在上面解析完參數并且确定好類型之後,這裡會通過實際的typeHandler來PreparedStatement對應的位置設定參數
           //注意這裡的+1操作。原始的JDBC裡面設定參數是從下标1開始的。之前已經将需要查詢的sql已經設定進去了。
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
           

執行查詢并且傳回結果

執行查詢

//PreparedStatementHandler方法
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //waring 這裡會真正的執行sql,下面就開始處理結果集
    ps.execute();
    
    //處理結果集映射
    return resultSetHandler.handleResultSets(ps);
  }
           

傳回結果,處理結果集映射。

關于這裡的代碼,我沒有詳細的看過,之後看完之後會有詳細分析一下。

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
   //這個集合就是用來存放sql查詢結果之後,封裝好的對象。
    final List<Object> multipleResults = new ArrayList<>();
    //将結果包裝成ResultSetWrapper,統計了每一列的類型和名字,還有對應的jdbc的資料類型
    int resultSetCount = 0;

    // 将 statement的ResultSet包裝為ResultSetWrapper。
    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);
      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); //将上面的改變,合并為一個單一的list。
  }
           

Mybatis中擷取擷取代理對象的之後的執行操作就分析到這裡了。如有不正确的地方,歡迎指出。謝謝。