手把手带你阅读Mybatis源码(二)执行篇(下)

简介: 手把手带你阅读Mybatis源码(二)执行篇(下)

执行阶段总结


到这里,MyBatis的执行阶段从宏观角度看,一共完成了两件事:


  1. 代理对象的生成


  1. SQL的执行


而SQL的执行用了大量的篇幅来进行分析,虽然是根据一条查询语句的主线来进行分析的,但是这么看下来一定很乱,所以这里我会话一个流程图来帮助大家理解:


image.png


结果集处理


在SQL执行阶段,MyBatis已经完成了对数据的查询,那么现在还存在最后一个问题,那就是结果集处理,换句话来说,就是将结果集封装成对象。在不用框架的时候我们是使用循环获取结果集,然后通过getXXXX()方法一列一列地获取,这种方法劳动强度太大,看看MyBatis是如何解决的。


@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
      ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
      //resultMap可以通过多个标签指定多个值,所以存在多个结果集
      final List<Object> multipleResults = new ArrayList<>();
      int resultSetCount = 0;
      //拿到当前第一个结果集
      ResultSetWrapper rsw = getFirstResultSet(stmt);
      //拿到所有的resultMap
      List<ResultMap> resultMaps = mappedStatement.getResultMaps();
      //resultMap的数量
      int resultMapCount = resultMaps.size();
      validateResultMapsCount(rsw, resultMapCount);
      //循环处理每一个结果集
      while (rsw != null && resultMapCount > resultSetCount) {
          //开始封装结果集 list.get(index) 获取结果集
        ResultMap resultMap = resultMaps.get(resultSetCount);
        //传入resultMap处理结果集 rsw 当前结果集(主线)
        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 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) {
            //判断resultHandler是否为空,如果为空建立一个默认的。
            //结果集处理器
            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());
      }
  }


上文的代码,会创建一个处理结果集的对象,最终调用handleRwoValues()方法进行行数据的处理。


//处理行数据
  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 {
          //不存在内嵌的结果集
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
      }
  }
  //没有内嵌结果集时调用
  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
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
      }
  }


这里的代码主要是通过每行的结果集,然后将其直接封装成一个Object对象,那么关键

就是在于getRowValue()方法,如何让行数据变为Object对象的。


private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
      //创建一个空的Map存值
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      //创建一个空对象装行数据
      Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())){
          //通过反射操作返回值
          //此时metaObject.originalObject = rowValue
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        if (shouldApplyAutomaticMappings(resultMap, false)) {
      //判断是否需要自动映射,默认自动映射,也可以通过resultMap节点上的autoMapping配置是否自动映射
            //这里是自动映射的操作。
          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中会判断是否是自动映射的,我们这里没有使用ResultMap,所以是自动映射(默认),那么就进入applyAutomaticMappings()方法,而这个方法就会完成对象的封装。


private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
        //自动映射参数列表
      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;
  }


我们可以看到这个方法会通过遍历参数列表从而通过metaObject.setValue(mapping.property, value);对返回对象进行赋值,但是返回对象有可能是Map,有可能是我们自定义的对象,会有什么区别呢?


实际上,所有的赋值操作在内部都是通过一个叫ObjectWrapper的对象完成的,我们可以进去看看它对于Map和自定义对象赋值的实现有什么区别,问题就迎刃而解了。


先看看上文中代码的metaObject.setValue()方法


public void setValue(String name, Object value) {
      PropertyTokenizer prop = new PropertyTokenizer(name);
      if (prop.hasNext()) {
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
          if (value == null) {
            // don't instantiate child path if value is null
            return;
          } else {
            metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
          }
        }
        metaValue.setValue(prop.getChildren(), value);
      } else {
          //这个方法最终会调用objectWrapper.set()对结果进行赋值
        objectWrapper.set(prop, value);
      }
  }


我们可以看看objectWrapper的实现类:


image.png


而我们今天举的例子,DemoMapper的返回值是Map,所以objectWrapper会调用MapWrapper的set方法,如果是自定义类型,那么就会调用BeanWrapper的set方法,下面看看两个类中的set方法有什么区别:


//MapWrapper的set方法
  public void set(PropertyTokenizer prop, Object value) {
      if (prop.getIndex() != null) {
        Object collection = resolveCollection(prop, map);
        setCollectionValue(prop, collection, value);
      } else {
        //实际上就是调用了Map的put方法将属性名和属性值放入map中
        map.put(prop.getName(), value);
      }
  }
  //BeanWrapper的set方法
  public void set(PropertyTokenizer prop, Object value) {
      if (prop.getIndex() != null) {
        Object collection = resolveCollection(prop, object);
        setCollectionValue(prop, collection, value);
      } else {
        //在这里赋值,通过反射赋值,调用setXX()方法赋值
        setBeanProperty(prop, object, value);
      }
  }
  private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
      try {
        Invoker method = metaClass.getSetInvoker(prop.getName());
        Object[] params = {value};
        try {
          method.invoke(object, params);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } catch (Throwable t) {
        throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
      }
  }


上面两个set方法分别是MapWrapper和BeanWrapper的不同实现,MapWrapper的set方法实际上就是将属性名和属性值放到map的key和value中,而BeanWrapper则是使用了反射,调用了Bean的set方法,将值注入。



image.png


结语


至此,MyBatis的执行流程就为止了,本篇主要聊了从构建配置对象后,MyBatis是如何执行一条查询语句的,一级查询语句结束后是如何进行对结果集进行处理,映射为我们定义的数据类型的。


由于是源码分析文章,所以如果只是粗略的看,会显得有些乱,所以我还是再提供一张流程图,会比较好理解一些。


image.png


当然这张流程图里并没有涉及到关于回写缓存的内容,关于MyBatis的一级缓存、二级缓存相关的内容,我会在第三篇源码解析中阐述。


欢迎大家访问博客:http://blog.objectspace.cn


END


相关文章
|
3月前
|
SQL XML Java
mybatis-源码深入分析(一)
mybatis-源码深入分析(一)
|
2月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
57 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
2月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
139 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
4月前
|
XML Java 数据库连接
mybatis源码研究、搭建mybatis源码运行的环境
这篇文章详细介绍了如何搭建MyBatis源码运行的环境,包括创建Maven项目、导入源码、添加代码、Debug运行研究源码,并提供了解决常见问题的方法和链接到搭建好的环境。
mybatis源码研究、搭建mybatis源码运行的环境
|
4月前
|
Web App开发 前端开发 关系型数据库
基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】
这篇文章介绍了一个基于SpringBoot+Vue+Redis+Mybatis技术栈开发的商城购物系统,包括系统功能、页面展示、前后端项目结构和核心代码,以及如何获取系统源码和答辩PPT的方法。
|
4月前
|
供应链 前端开发 Java
服装库存管理系统 Mybatis+Layui+MVC+JSP【完整功能介绍+实现详情+源码】
该博客文章介绍了一个使用Mybatis、Layui、MVC和JSP技术栈开发的服装库存管理系统,包括注册登录、权限管理、用户和货号管理、库存管理等功能,并提供了源码下载链接。
服装库存管理系统 Mybatis+Layui+MVC+JSP【完整功能介绍+实现详情+源码】
|
4月前
|
缓存 Java 数据库连接
我要手撕mybatis源码
该文章深入分析了MyBatis框架的初始化和数据读写阶段的源码,详细阐述了MyBatis如何通过配置文件解析、建立数据库连接、映射接口绑定、动态代理、查询缓存和结果集处理等步骤实现ORM功能,以及与传统JDBC编程相比的优势。
我要手撕mybatis源码
|
2月前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
119 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
58 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
358 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个

相关实验场景

更多
下一篇
无影云桌面