# MyBatis源码-mapper解析

这一节我们来看看 MyBatis 如何读取配置文件,通过 mapper 文件解析查询结果

# 示例代码

        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        // 第二阶段:数据读写阶段
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 找到接口对应的实现
            UserMapper userMapper = session.getMapper(UserMapper.class);
            // 组建查询参数
            User userParam = new User();
            userParam.setSchoolName("Sunny School");
            // 调用接口展开数据库操作
            List<User> userList =  userMapper.queryUserBySchoolName(userParam);
            // 打印查询结果
            for (User user : userList) {
                System.out.println("name : " + user.getName() + " ;  email : " + user.getEmail());
            }
        }

# 大概流程:

image-20241203100832189

# 获取 mapper 代理

我们从示例代码一步一步来分析,首先看获取 mapper 对象:

UserMapper userMapper = session.getMapper(UserMapper.class);

上面代码通过 SqlSession 获取 mapper 返回的是一个代理对象,通过这个代理对象调用相应方法时才能把查询结果映射到实体类。

源码如下:

//MapperRegistry类
//从这个方法里边获取 mapper 代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //knownMappers 是一个map  Map<Class<?>, MapperProxyFactory<?>> knownMappers
    //根据对象类型获取代理工厂  这里基本上都能拿到结果,因为系统初始化的时候已经初始化了相应对象,具体看下边源码。
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

mapper 代理工厂 knownMappers 的值是在什么时候创建的呢?看下边的源码:

//MapperRegistry类
//通过这个方法添加 mapper 代理对象  
//该方法 系统启动的时候就会调用
public <T> void addMapper(Class<T> type) {
    //是否是接口
    if (type.isInterface()) {
        //是否已经创建了改 mapper 对象
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
          //通过注解的方式解析
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

下面对以上内容总结:

最重要的是 getMapper 拿到的是一个代理对象,这样在后续执行方法的时候会被代理对象拦截。

# 执行具体的方法

当拿到具体的 mapper 代理对象后,就要开始执行具体的方法了,比如:

List<User> userList =  userMapper.queryUserBySchoolName(userParam);

调用 queryUserBySchoolName 方法,最终执行的就是 MapperProxy 代理类,代理会拦截方法去解析 xml 文件对应的 sql , 然后把结果处理完返回。

//MapperProxy 类
//调用mapper具体方法都会走到这里
@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //getDeclaringClass() 返回声明由此 Method 对象表示的方法的类的 Class 对象
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
      //先从缓存中获取方法信息
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      //开始执行方法 args 为查询该方法的相关参数, execute 看下边源码。
    return mapperMethod.execute(sqlSession, args);
  }
// MapperMethod 类
// 这个方法具体执行对应的增、删、改、查
// 方法具体做了,根据参数和方法信息查询并组装sql,执行sql把结果设置到resultMap 对象中。
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);
            //返回结果是 map
        } 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());
    }
    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;
  }

以下面的示例代码, 返回的是一个 list 结果集,自然走到 executeForMany 这个方法:

List<User> userList =  userMapper.queryUserBySchoolName(userParam);
// MapperMethod 类
// 查询结果是列表形式
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    //查询参数是否包含分页参数  RowBounds
    if (method.hasRowBounds()) {
        //如果包含分页参数,解析对象
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
        //这个方法是处理没有包含分页参数的,但调用的方法和分页参数都是一个,只不过后面会传递一个默认的分页参数 RowBounds.DEFAULT
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //mapper 定义的返回结果类型和查询类型不符时处理
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

selectList 方法一路跟踪下来,走到 query 查询这里,这里具体操作数据库。

//PreparedStatementHandler 类
//真正执行查询方法
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //一个代理对象,目的是增加了查询参数日志
    PreparedStatement ps = (PreparedStatement) statement;
    //开始执行
    ps.execute();
    //处理返回结果,该方法源码看下面。
    return resultSetHandler.handleResultSets(ps);
  }

下面对以上内容总结:

拿到 mapper 的代理对象,执行具体方法判断该方法是增、删、改、查哪一种,然后走不同分支处理。

系统在启动的时候,已经把 xml 文件里边的方法及对应的 sql 语句进行了解析,当执行具体方法是会从配置里边查找对应的 xml 方法,拿到执行语句及 resultMap 也就是返回对象。然后执行语句把结果映射到对象中。

# 解析查询结果

//DefaultResultSetHandler类
//解析查询结果
@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) {
        //获取xml 方法所对应的返回结果类
      ResultMap resultMap = resultMaps.get(resultSetCount);
        //这一步就是把结果解析为对应的实体类!!! multipleResults 就是数据库查询数据处理为结果集
      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);
  }

以上代码说的是当我们拿到查询结果,获取结果集:

首先,判断返回结果集是否是多个,存储过程形式可能返回多个结果集;

第二步,校验该查询方法对应是否存在 resultType 对应内容;

第三步,把 ResultMap 及 ResultSet 传递给 handleResultSet 方法进行参数结果解析映射;

//DefaultResultSetHandler类
//处理结果  
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
          //处理 存储过程的 resultSets
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
            //使用默认结果处理器
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            //见下面详细源码
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            //处理完这个获取结果集并添加到multipleResults
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
            //用户自定义 resultHandler 
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

上述代码是结果处理,主要是处理存储过程、自定义处理器、默认处理器这3种形式。

//DefaultResultSetHandler类
//处理结果  
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //是否是嵌套映射
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        //处理数据库查询的结果然后映射到resultMap上面,具体见下面源码
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }
//DefaultResultSetHandler类
//处理数据库查询的结果然后映射   
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    //存放结果上下文,每次存放最近一条记录
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    //分页 跳过相应行
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        //获取解析并映射的结果
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        //保存结果
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }
//DefaultResultSetHandler类
//获取行具体结果   
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //创建result返回对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        //把返回对象转换为MetaObject  这里边包含了类的相关信息及反射
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

上述代码还是在处理结果,比较重要的就是 getRowValue 方法。

第一步,根据 resultType 创建一个空对象;

第二步,把创建的对象通过 MetaObject 包装一下,MetaObject 会把对象字段及方法进行处理,然后暴露相关方法,可以理解为反射工具类;

第三步,通过 applyAutomaticMappings 方法进行字段赋值;

//DefaultResultSetHandler类
//这个方法是拿到数据库 ResultSet 然后再根据 resultMap 字段进行映射
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    //创建一个对象里边字段属性的list ,包含字段名称 、字段类型
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
          //这里就是通过字段名称获取到数据库查询出来的具体值了!!!
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
            //通过反射把值设置进去
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

参考文章:

  1. MyBatis ResultSetHandler 结果集解析过程 (https://juejin.cn/post/7198821253037391909 )
  2. MyBatis handleResultSet 结果集解析过程 (https://juejin.cn/post/7199577622120202300 )
上次更新: 2024/12/11