mybatis源码学习之执行过程分析(5)——sql执行后ResultSet的处理及结果返回

本文深入剖析MyBatis如何将数据库查询结果封装为POJO对象。从PreparedStatementHandler到DefaultResultSetHandler,逐步揭示ResultSetWrapper的作用及其内部实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mybatis源码学习及分析之执行过程分析(5)——返回结果的处理

上一篇中分析了sql语句的创建和执行过程,使用jdbc时,执行完sql后会返回ResultSet,然后我们会通过getInteger()、getString()等方法拿到数据。而使用Mybatis中我们可以直接将结果转换为POJO对象,下面就来看看mybatis是如何封装ResultSet的。

PreparedStatementHandler.java

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //在这里开始结果处理
    return resultSetHandler.<E> handleResultSets(ps);
  }

这里用到了ResultSetHandler。

这里写图片描述

DefaultResultSetHandler.java

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mapdpedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    //获取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);
  }


  private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    //JDBC中的代码,在这里拿到了ResultSet 
    ResultSet rs = stmt.getResultSet();
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    //将ResultSet包装为ResultSetWrapper
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }

当前调用栈信息:
这里写图片描述

当ResultSet不为空时,也就是查到了结果,通过rs和Configuration实例化了ResultSetWrapper。

public class ResultSetWrapper {

  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final List<String> columnNames = new ArrayList<String>();
  private final List<String> classNames = new ArrayList<String>();
  private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>();
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>();
  private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>();
  private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    //从configuration中拿到了typeHandlerRegistry 
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;

    //拿到了列信息
    final int columnCount = metaData.getColumnCount();
    //将每一列的名称、jdbc类型、java类型分别保存在三个ArrayList中。
    for (int i = 1; i <= columnCount; i++) { 
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }
  。。。
 }

当前调用栈如下图:
这里写图片描述

返回的ResultSetWrapper信息如图:
这里写图片描述

到这里,我们的调用栈就返回到了DefaultResultHandler#handleResultSets(Statement stmt)

接下来通过mappedStatement拿到了xml中配置的ResultMap。
这里写图片描述

可以看到ResultMap是我们将jdbc类型数据映射为POJO对象的重要手段。

validateResultMapsCount(rsw, resultMapCount);

对ResultMap进行校验,当查询返回的ResultMap数量小于1时,说明没有找到对应的ResultType或者ResultMap可以将结果映射出来,在这里就会抛出ExecutorException异常。

接下来重要的操作就是handleResultSet(rsw, resultMap, multipleResults, null);这里开始对结果集进行封装。

DefaultResultSetHandler.java

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //处理行信息
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
            //调用
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }


  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      //调用getRowValue
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }

  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //创建了User
    Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        //拿到列信息
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
  }

//调用User的setter方法为User实例设置信息
  private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
          //getPropertyMappingValue在这里对了数据转换
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          //在这里使用了setter方法设置值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

这里写图片描述
可以看到调用了User的setName。
这里写图片描述


  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERED;
    } else {
       //typeHandler
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      //数据库表的列名
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      //得到数据库中信息
      return typeHandler.getResult(rs, column);
    }
  }

当所有的值都设置完成后,已经得到了我们需要的POJO类信息,调用栈开始出栈操作,方法return。
这里写图片描述

至此,结果封装完成,所有调用栈开始出栈操作,最后返回我们需要的User。

<think>嗯,用户这次的问题很明确,是要找关于JavaWeb开发图书馆管理系统在IDEA环境下的教程或示例代码。从用户提到的两个站内引用来看,虽然一个是图书管理系统,一个是待办事项应用,但确实能提炼出一些共通的关键技术点。用户可能是个刚开始接触JavaWeb的学生或者转行者,正在做课程设计或者毕业设计。ta特意强调IDEA开发环境,说明可能刚接触这个IDE,需要手把手指导。从ta引用站内资料的行为来看,ta应该已经做过一些基础搜索,但没找到完全匹配的教程,所以有些着急。我之前给的回复结构是合理的:先分环境准备、核心模块、进阶优化三部分,每个部分都结合了引用内容的核心技术点。比如创建项目的操作细节是根据引用[2]中的SpringBoot项目创建步骤展开的,但替换成了图书管理场景。用户既然能提供这些引用,说明ta有一定理解能力,所以我在最后补充的"注意事项"里强调了项目驱动学习的重要性。注意到用户上次提问时带LaTeX格式要求,这次虽然没提但还是要保持规范。所有技术名词如JDBC、Tomcat都用$包裹,代码块保持独立格式。在介绍MVC模式时,特意把Model-View-Controller拆开用$$单独标注,这样学究用户应该会满意。给的示例代码片段是经过斟酌的——选登录和图书查询这两个最基础的功能,避免给初学者造成压力。MyBatis的配置说明特意标出引用[2]的关联内容,这样用户若想深入看原文也能找到依据。性能优化那块提到Redis缓存时标注[^2],因为待办事项应用引用里确实提到过SpringData技术栈。最后三个补充问题不是随便列的。用户连续两次追问图书管理系统,说明ta很可能在开发中遇到具体瓶颈:"部署教程"针对环境配置问题,"登录模块"针对初学者常见的权限控制困惑,"添加图书"则是基础CRUD操作的核心。这比问"怎么优化"之类笼统问题更实用。</think> 以下是为您整理的**基于 IntelliJ IDEA 的 JavaWeb 图书馆管理系统**开发教程和资源指南,结合站内引用优化实现步骤: --- ### **一、环境准备(IDEA 版)** 1. **工具安装** - JDK 1.8+(推荐 JDK 11) - IntelliJ IDEA Ultimate版(社区版缺少 JavaEE 支持) - Apache Tomcat 9.0+ - MySQL 5.7+ 或 MariaDB - Maven 3.6+(项目管理) 2. **项目初始化** ```mermaid graph LR A[创建项目] --> B[选择 Maven 项目] B --> C[勾选 Web Application 模板] C --> D[配置 GroupId:com.library] D --> E[ArtifactId:library-management] ``` --- ### **二、核心模块开发流程** #### 1. **数据库设计** ```sql CREATE TABLE books ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, author VARCHAR(50), isbn VARCHAR(20) UNIQUE, status ENUM('AVAILABLE','BORROWED') DEFAULT 'AVAILABLE' ); -- 更多表:users/borrow_records... ``` #### 2. **MVC 分层实现** | 层 | 路径 | 示例类名 | |------------|-----------------------|---------------------| | Model | `src/main/java/entity` | `Book.java` | | DAO | `src/main/java/dao` | `BookDao.java` | | Service | `src/main/java/service`| `BookService.java` | | Controller | `src/main/java/servlet`| `BookServlet.java` | | View | `web/WEB-INF/jsp` | `book_list.jsp` | #### 3. **关键技术实现** ```java // DAO 层示例 (JDBC) public class BookDao { public List<Book> getAllBooks() throws SQLException { String sql = "SELECT * FROM books"; try (Connection conn = DataSourceUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ResultSet rs = ps.executeQuery(); // 结果集转对象列表 } } } ``` #### 4. **Servlet 交互示例** ```java @WebServlet("/book/list") public class BookListServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { List<Book> books = new BookService().getAllBooks(); req.setAttribute("books", books); req.getRequestDispatcher("/book_list.jsp").forward(req, resp); } } ``` --- ### **三、IDEA 特有配置** 1. **Tomcat 集成** - `Run → Edit Configurations → + → Tomcat Server` - 部署 WAR 包到 `Tomcat/webapps` 2. **数据库连接池** 使用 **HikariCP**(需在 `pom.xml` 添加依赖): ```xml <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> ``` --- ### **四、推荐学习资源** 1. **视频教程** - [B站:IDEA+JavaWeb图书管理系统实战](https://www.bilibili.com/video/BV1q4411i7gL)(含完整源码) 2. **开源项目参考** - GitHub 项目:[Library-Management-System-JavaWeb](https://github.com/example/library-system)(Star 1.2k+,含数据库脚本) 3. **关键文档** - [IntelliJ IDEA 官方 JavaEE 指南](https://www.jetbrains.com/help/idea/javaee.html) --- ### **五、注意事项** 1. 避免直接拷贝代码,建议从 **登录模块** 开始分模块实现 2. 前端建议使用 **Bootstrap 5** 快速构建界面 3. 重点掌握 **$$ \text{[ 请求 → Servlet → JSP 数据流转 ]} $$** 流程 > 附:项目结构示例 > ``` > src > ├─main > │ ├─java > │ │ ├─controller > │ │ ├─dao > │ │ └─entity > │ ├─resources > │ └─webapp > │ └─WEB-INF > └─test > ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值