Mybatis中获取获取代理对象的之后的执行操作

Mybatis中获取获取代理对象的之后的执行操作

之前已经说了Mybatis中mapper是怎么和XMl关联起来的已经分析了,Mybaits从XML是怎么和Mapper对应上来的。这里从宏观上介绍一下是怎么执行的。之后会在具体的博客里面介绍。

1. 几个重要的步骤

  1. 调用方法的时候调用MapperProxyinvoke方法,并且构建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在创建它的时候。会创建ParameterHandlerResultSetHandler,并且调用插件方法包装。

    6. 调用StatementHandlerprepare方法

      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中的ExecutorCachingExecutor,如果没有开启就是直接是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在创建它的时候。会创建ParameterHandlerResultSetHandler,并且调用插件方法包装

创建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中获取获取代理对象的之后的执行操作就分析到这里了。如有不正确的地方,欢迎指出。谢谢。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值