zoukankan      html  css  js  c++  java
  • mybatis查询语句的背后之封装数据

     转载请注明出处。。。

    一、前言

    继上一篇mybatis查询语句的背后,这一篇主要围绕着mybatis查询的后期操作,即跟数据库交互的时候。由于本人也是一边学习源码一边记录,内容难免有错误或不足之处,还望诸位指正,本文只可当参考作用。谨记! 

    二、分析

    继上一篇博文的查询例子,mybatis在最后的查询最终会走SimpleExecutor类的doQuery方法,

     1  @Override
     2   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     3     Statement stmt = null;
     4     try {
     5       Configuration configuration = ms.getConfiguration();
     6       // 这里也就是采用了策略模式(个人感觉有点像),实际的statementHandler为routingStatementHandler
     7       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     8       stmt = prepareStatement(handler, ms.getStatementLog());
     9       // 虽然是执行的routingStatementHandler.query,但返回结果的还是PreparedStatementHandler处理
    10       return handler.query(stmt, resultHandler);
    11     } finally {
    12       closeStatement(stmt);
    13     }
    14   }
    15 
    16 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    17     Statement stmt;
    18    // 使用了代理模式,也可以理解为对connection进行了一层包装,这里的作用就是加了log处理
    19     Connection connection = getConnection(statementLog);
    20    //进行预编译,即类似jdbc的 sql,如 select * from user where id=?
    21     stmt = handler.prepare(connection, transaction.getTimeout());
    22     // 对执行查询的sql进行参数设置
    23     handler.parameterize(stmt);
    24     return stmt;
    25   }

    关于 handler.prepare的作用这里简单介绍下,不做代码分析。

    会设置fetchSize,作用就是一次性从数据库抓取数据,好像默认值是10条,如果每次只抓取一条,则进行rs.next的时候,会再次查库。

    如果是insert操作,并且数据库主键自增且还设置了可以返回主键,则会还做获取主键的操作。

    先从设置参数说起,也就是handler.parameterize。先看下源码,具体位置在DefaultParameterHandler类里面

     1 @Override
     2   public void setParameters(PreparedStatement ps) {
     3     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     4     // 获取配置文件里面的sql参数信息,如sql为select * from user where id=#{userId,jdbcType=INTEGER}
     5     // ParameterMapping 记录了参数名也就是userId,还有记录了对应的jdbc类型,还有对应的javaType等等,具体可以debug看下
     6     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
     7     if (parameterMappings != null) {
     8       for (int i = 0; i < parameterMappings.size(); i++) {
     9         ParameterMapping parameterMapping = parameterMappings.get(i);
    10         if (parameterMapping.getMode() != ParameterMode.OUT) {
    11           Object value;
    12           String propertyName = parameterMapping.getProperty();
    13           // 如果为true,那么sql参数中有类似 user.name 格式
    14           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
    15             value = boundSql.getAdditionalParameter(propertyName);
    16           } else if (parameterObject == null) {
    17             value = null;
    18           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    19             value = parameterObject;
    20           } else {
    21             // metaObject 类似一个工具类,它里面有一个反射工厂,可以专门解析一个类的信息,如字段的setter/getter/属性信息,这里不做多余介绍
    22             // 1、下面详细介绍 
    23             MetaObject metaObject = configuration.newMetaObject(parameterObject);
    24             value = metaObject.getValue(propertyName);// 取值
    25           }
    26           // 获取对应的typeHandler,一般情况不设置的话,基本都是ObjectTypeHandler
    27           TypeHandler typeHandler = parameterMapping.getTypeHandler();
    28           JdbcType jdbcType = parameterMapping.getJdbcType();
    29           if (value == null && jdbcType == null) {
    30             jdbcType = configuration.getJdbcTypeForNull();
    31           }
    32           try {
    33             // 进行设值
    34             typeHandler.setParameter(ps, i + 1, value, jdbcType);
    35           } catch (TypeException e) {
    36             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    37           } catch (SQLException e) {
    38             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    39           }
    40         }
    41       }
    42     }
    43   }

     对于上述代码中的一部分这里负责将parameterObject的里面的值整出来(也就是传入的参数),如果参数是map结构,就从map里面取值,如果不是,如单个非javabean参数,则直接取值,如果是单个javabean,则通过metaObject类转换成一个BeanWrapper,进行取值

    这段代码也就负责对预编译后的sql设置参数,这里逻辑主要是围绕以下步骤进行得,

        获取参数名,获取参数值,获取参数类型,然后做进行设值操作

     1  /**
     2  * mybatis数据处理有单结果集和多结果集处理,一般多结果集出现存储过程中,如果存储过程中写了两条select语句,如
     3  * select * from user , select * from classes   这种情况这里不做介绍,因为本人用的不多,理解的也不是很透彻。
     4  * 这里不多做介绍,这里只针对简单映射做一个大概介绍
     5  *
     6  */
     7 public List<Object> handleResultSets(Statement stmt) throws SQLException {
     8     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
     9     // 保存查询结果
    10     final List<Object> multipleResults = new ArrayList<>();
    11 
    12     int resultSetCount = 0;
    13     // 获取第一条数据
    14     ResultSetWrapper rsw = getFirstResultSet(stmt);
    15     // 如果不是多结果集映射,一般resultMaps的大小为1
    16     // resultMap中存储的有类的字段属性,数据库字段名称等信息
    17     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    18     int resultMapCount = resultMaps.size();
    19     // 校验数据的正确性
    20     validateResultMapsCount(rsw, resultMapCount);
    21     while (rsw != null && resultMapCount > resultSetCount) {
    22       ResultMap resultMap = resultMaps.get(resultSetCount);
    23       // 处理结果集映射
    24       handleResultSet(rsw, resultMap, multipleResults, null);
    25       rsw = getNextResultSet(stmt);
    26       cleanUpAfterHandlingResultSet();
    27       resultSetCount++;
    28     }
    29     // 处理slect 标签的resultSets属性,多个用逗号隔开,个人几乎没用过,略过
    30     String[] resultSets = mappedStatement.getResultSets();
    31     if (resultSets != null) {
    32       while (rsw != null && resultSetCount < resultSets.length) {
    33         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
    34         if (parentMapping != null) {
    35           String nestedResultMapId = parentMapping.getNestedResultMapId();
    36           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
    37           handleResultSet(rsw, resultMap, null, parentMapping);
    38         }
    39         rsw = getNextResultSet(stmt);
    40         cleanUpAfterHandlingResultSet();
    41         resultSetCount++;
    42       }
    43     }
    44 
    45     return collapseSingleResultList(multipleResults);
    46   }

    以上代码就是为结果映射做一个铺垫,重点是在hanleResultSet方法里,

     1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
     2     try {// 针对简单映射,parentMapping是为Null的
     3       if (parentMapping != null) {
     4         handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
     5       } else {
     6         // 默认使用defaultResultHandler,如需使用自定义的,则可在传参加入resultHandler接口实现类
     7         if (resultHandler == null) {
     8           DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
     9           // 处理结果,结果存在resultHandler里
    10           handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
    11           multipleResults.add(defaultResultHandler.getResultList());
    12         } else {
    13           handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
    14         }
    15       }
    16     } finally {
    17       // issue #228 (close resultsets)
    18       closeResultSet(rsw.getResultSet());
    19     }
    20   }
    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);
        }
      }
     1 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
     2       throws SQLException {
     3     DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
     4     ResultSet resultSet = rsw.getResultSet();
     5     // 跳过多少行,到达指定记录位置,如在传参的时候传入了rowBounds,则会根据该类的offset值跳到指定记录位置
     6     skipRows(resultSet, rowBounds);
     7     // shouldProcessMoreRows 用来检测是否能继续对后续的结果进行映射
     8     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
     9       //用来处理resultMap节点中配置了discriminator节点,这里忽略掉
    10       ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    11       // 得到的结果就是sql执行后的一行记录,如返回User对象信息,则rowValue就代表一个user实例,里面已经有值了
    12       Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    13       //保存数据
    14       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    15     }
    16   }
     1 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
     2     final ResultLoaderMap lazyLoader = new ResultLoaderMap();
     3     // 创建对象,可以理解为对resultMap节点的type属性值,进行了反射处理,得到了一个对象,但属性值都是默认值。
     4     Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
     5     if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
     6       final MetaObject metaObject = configuration.newMetaObject(rowValue);
     7       boolean foundValues = this.useConstructorMappings;
     8       //是否需要自动映射,有三种映射,分别为None,partial,full,默认第二种,处理非嵌套映射,可通过autoMappingBehavior 配置
     9       if (shouldApplyAutomaticMappings(resultMap, false)) {
    10         // 映射resultMap中未明确指定的列,如类中含有username属性,但是resultMap中没配置,则通过这个进行数据映射,还是可以查询到结果
    11         foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    12       }
    13       // 处理resultMap中指定的列
    14       foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    15       foundValues = lazyLoader.size() > 0 || foundValues;
    16       // 如果没查询到结果,但配置可返回空对象(指的是没有设置属性值得对象),则返回空对象,否则返回null
    17       rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    18     }
    19     return rowValue;
    20   }

    这里只介绍resultMap中有明确指定的列

     1 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
     2       throws SQLException {
     3      // 获取数据字段名
     4     final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
     5     boolean foundValues = false;
     6     // 获取的数据就是resultMap节点中配置的result节点,有多个result节点,这个集合大小就是多少
     7     // 里面存储的是属性名/字段名等信息
     8     final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
     9     for (ResultMapping propertyMapping : propertyMappings) {
    10       String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    11       // 是否有嵌套映射
    12       if (propertyMapping.getNestedResultMapId() != null) {
    13         // the user added a column attribute to a nested result map, ignore it
    14         column = null;
    15       }
    16       // 针对1来说一般常与嵌套查询配合使用
    17       // 2 判断属性基本映射
    18       // 3 多结果集的一个处理
    19       if (propertyMapping.isCompositeResult()// 1
    20           || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 2
    21           || propertyMapping.getResultSet() != null) {// 3
    22         // 获取当前column字段对于的值,有用到typeHandler来进行参数的一个转换
    23         Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
    24         
    25         //获取类的属性字段名
    26         final String property = propertyMapping.getProperty();
    27         if (property == null) {
    28           continue;
    29         } else if (value == DEFERRED) {// 类似占位符。处理懒加载数据
    30           foundValues = true;
    31           continue;
    32         }
    33         if (value != null) {
    34           foundValues = true;
    35         }
    36         if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
    37           // 进行设置属性值
    38           metaObject.setValue(property, value);
    39         }
    40       }
    41     }
    42     return foundValues;
    43   }

    或许有人奇怪为啥没看到查询的对象有set操作,值就到了对象里面去了,这里全是metaObject给你操作了,具体的,大家可以自行了解这个类,只能说这个类的功能很强大。

    以上就是本文全部内容,

    --------------------------------------------------------------------------------------------------------------------------分界线----------------------------------------------------------------------------------------------

  • 相关阅读:
    美国诚实签经验——必带材料:护照,证件照,DS160确认页,面试预约确认页,+境外照片
    美国诚实签经验——医院预约单和医院资料,医生预约收据和报价表,赴美预算,赴美行程,保险是加分项,工作证明(勾出职位和薪酬),附上名片或者工卡,全家福照片
    php MySQLi部分函数(面向对象和过程)
    php calling scope
    mysqli_set_charset和SET NAMES优劣分析
    php 父类子类构造函数注意事项
    Dreamweaver显示花括号匹配
    php 父类调用子类方法和成员
    PHP 重载 __call() _callStatic方法
    转:Linus:利用二级指针删除单向链表
  • 原文地址:https://www.cnblogs.com/qm-article/p/10588627.html
Copyright © 2011-2022 走看看