Mybatis之SqlSessionFactoryBean源码初步解析(二)主写怎么解析resultMap以及查询语句

这边文章接Mybatis之SqlSessionFactoryBean源码初步解析(一)。

直接进入主题

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

首先说一下mybatis 里面的分页是假分页, 就是给数据全部拿到手,再去分页。脱裤子放屁,不看

先看context里面是什么内容:

因为用的逆向工程自动生成的 所以内容很多,就不全部截图下来了。

一 、解析mapper.xml文件

解析之前先给全部的resultMap标签找到

为了方便回头看,具体内容如下:

[<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
<id jdbcType="BIGINT" column="id" property="id"/>
<result jdbcType="VARCHAR" column="sname" property="sname"/>
<result jdbcType="INTEGER" column="sno" property="sno"/>
<result jdbcType="VARCHAR" column="password" property="password"/>
<result jdbcType="INTEGER" column="power" property="power"/>
</resultMap>
]

解析规则

首先要特别说一句,比如A的子元素是B ,B的子元素是C 那么C就不是A 的子元素。

在for循环挨个解析resultMap元素

最终的解析方法在这里:

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

resultMapNode.getStringAttribute('A','B');这个方法很简单, 我看了一下,意思就是xnode 的attribute 属性里面如果key A 对应的value 是null 那么就将B 的值赋给A ;attribute的类型是Property继承自HashTable;

那么下面代码的意思是:

 String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));

如果啊resultMapNode里面没有type, 就去找ofType,没有的话就去找resultType,再没有就去找javaType;

resultMapNode里面是什么呢?

 

<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
<id jdbcType="BIGINT" column="id" property="id"/>
<result jdbcType="VARCHAR" column="sname" property="sname"/>
<result jdbcType="INTEGER" column="sno" property="sno"/>
<result jdbcType="VARCHAR" column="password" property="password"/>
<result jdbcType="INTEGER" column="power" property="power"/>
</resultMap>

比上面的少一对[] ,就是list 里面的一个元素;

解析别名

Class<?> typeClass = resolveClass(type);这个方法我看了就是去解析别名的,存在一个final  Map中。key值为class 的全路径名。

resultMappings 我们先不管, 先看resultChildren 

[<id jdbcType="BIGINT" column="id" property="id"/>
, <result jdbcType="VARCHAR" column="sname" property="sname"/>
, <result jdbcType="INTEGER" column="sno" property="sno"/>
, <result jdbcType="VARCHAR" column="password" property="password"/>
, <result jdbcType="INTEGER" column="power" property="power"/>
]

看下面的判断如果他的子标签是constructor或者是discriminator。会调用processDiscriminatorElement(resultChild, typeClass, resultMappings);这个方法其实就是在剥一层而已。然后如果是constructor则和result标签一样掉buildResultMappingFromContext方法将解析好的东西放入resultMappings中。

那么重点就在buildResultMappingFromContext中了。下面上代码:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property = context.getStringAttribute("property");
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
  

context 内容为:<id jdbcType="BIGINT" column="id" property="id"/>即为resultChildren中的第一个元素

上面的没什么好看的就是解析标签内个元素标签。还有别名

二、封装解析出来的标签

最后的最后调用buildResultMapping方法,代码如下

public ResultMapping buildResultMapping(
      Class<?> resultType,
      String property,
      String column,
      Class<?> javaType,
      JdbcType jdbcType,
      String nestedSelect,
      String nestedResultMap,
      String notNullColumn,
      String columnPrefix,
      Class<? extends TypeHandler<?>> typeHandler,
      List<ResultFlag> flags,
      String resultSet,
      String foreignColumn,
      boolean lazy) {
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
    List<ResultMapping> composites = parseCompositeColumnName(column);
    if (composites.size() > 0) {
      column = null;
    }
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        .jdbcType(jdbcType)
        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
        .resultSet(resultSet)
        .typeHandler(typeHandlerInstance)
        .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
        .composites(composites)
        .notNullColumns(parseMultipleColumnNames(notNullColumn))
        .columnPrefix(columnPrefix)
        .foreignColumn(foreignColumn)
        .lazy(lazy)
        .build();
  }

这是干了什么? 其实就是用了一个构建模式。这边真的要好好看看Builder是ResultMapping里面的静态内部类,Builder里面有个resultMapping变量,怎么设置值呢? 我抽了一个代码看看,这个代码nestedQueryId 很明显是个很重要的东西,我忘了查看:

 public Builder nestedQueryId(String nestedQueryId) {
      resultMapping.nestedQueryId = nestedQueryId;
      return this;
    }

return的是this ,this 就是ResultMapping这个类本身的一个实例。最后调用.build的方法就是组装好了,然后返回Build中的resultMapping 变量。最后我们再将这个resultMapping放入ResultMappings里面;再看下面的代码:

  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    }

意思是将resultMappings 传到ResultMapResolver中,同样里面也是构建模式。最后返回一个ResultMap类型;

可能这样会把大家绕晕了,我以下面的东西解释吧:

1.<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
2.<id jdbcType="BIGINT" column="id" property="id"/>
3.<result jdbcType="VARCHAR" column="sname" property="sname"/>
4.<result jdbcType="INTEGER" column="sno" property="sno"/>
5.<result jdbcType="VARCHAR" column="password" property="password"/>
6.<result jdbcType="INTEGER" column="power" property="power"/>
7.</resultMap>

除了第一行和第七行,其他每一行都是一个resultMapping. 第二行到第6 行的总和组成了resultMappings。

最后第一行到第七行整个组成了一个resultMap.

下图是通过本章第一块代码的

 

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

调过来的,作用是解析所有select|insert|update|delete 放入一个list 中

 private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

这边我们可以看到不管databaseid是不是空都会掉到下面

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

 

这里其实就是遍历list 一个一个解析,每一个查询标签都会对应一个statementParser,进入paserStatementNode方法

 

比如id  等等属性都会被先解析,最重要的一句代码是这个  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);我们先将这个高潮缓一下。先看看另外一个方法干了什么。

  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

 

builderAssistant.addMappedStatement()方法的参数是解析好的属性, 他干了什么呢?

 

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

这一看就是构建模式将各种属性放入进去,最终将调用configuration的方法将之放入大管家的一个Map中。这边有个resource属性,如果我们记错的话,这个resource是类名。

我们回头看这个createsqlSource 方法,

最重要的在下面展示的代中

三、解析增删查改的xml标签

  List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return contents;
  }
<select id="selectByExample" resultMap="BaseResultMap" parameterType="com.fuchanghai.mybatis.pojo.DailyExample" >
    1)select
    2)<if test="distinct" >
      distinct
    </if>3)
    4)<include refid="Base_Column_List" />
   5) from daily
  6) <if test="_parameter != null" >
      <include refid="Example_Where_Clause" />
    </if>7)
    8)<if test="orderByClause != null" >
      order by ${orderByClause}
    </if>9)
  </select>

图上的NodeList children = node.getNode().getChildNodes();children 的长度为9,这些标签中的内嵌的元素不在children中。children中只包含直接子标签这一点很重要。有人想问,3)7)9)是什么子元素?额。。。。是换行。。

3对应的图下的2了解一下

我们现在主要解析这句有代表性的:

<if test="_parameter != null">
<where>
<foreach item="criteria" separator="or" collection="oredCriteria">
<if test="criteria.valid">
<trim prefix="(" suffix=")" prefixOverrides="and">
<foreach item="criterion" collection="criteria.criteria">
<choose>
<when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
<when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
<when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
<when test="criterion.listValue">
<foreach open="(" item="listItem" separator="," collection="criterion.value" close=")">
                    #{listItem}
                  </foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</if>

首先是if 标签,if 标签不是静态标签那么他就会走下面的代码

 else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }

nodeName是 if  

nodeHandlers(nodeName);所调用的方法如下

 NodeHandler nodeHandlers(String nodeName) {
    Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
    map.put("trim", new TrimHandler());
    map.put("where", new WhereHandler());
    map.put("set", new SetHandler());
    map.put("foreach", new ForEachHandler());
    map.put("if", new IfHandler());
    map.put("choose", new ChooseHandler());
    map.put("when", new IfHandler());
    map.put("otherwise", new OtherwiseHandler());
    map.put("bind", new BindHandler());
    return map.get(nodeName);
  }

即根据标签名称从Map中取得对应的处理标签的类

handler.handleNode(child, contents);我们这边是动态标签所以会掉到这里,这个contents 对应的是select 查询语句级别的 我们暂且给看看成全局的.

进入IfHandler的解析方法

   public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);

    }

因为if标签是动态标签所以我们会继续交给parseDynamicTags(nodeToHandle)来解析。很明显这是个递归的调用。只有当标签是StaticText类型的才会停止递归,进行下一个元素的解析。(这里以我的口述能力实在不知道该怎么去解释)

最终会将解析好的List <sqlNode> 封装成MixedSqlNode, z再将mixedsqlnode 封装成ifSqlNode 将之放入全局的contexts中。最终将全局的contexts分装成MixedSqlNode ,最最最后再分装成SqlSource。实际上我说出了递归,大家应该就知道的差不多了

转载于:https://my.oschina.net/u/4042146/blog/2980095

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值