美文网首页
mybatis源码分析-资源加载-下篇

mybatis源码分析-资源加载-下篇

作者: cjxz | 来源:发表于2018-12-20 16:35 被阅读0次

处理mapper节点

构造函数中已经有很多很多默认类型匹配。这就是为什么在写sql的时候返回类型会自动映射到相应的java类型上面,这里已经处理好了。继续看最复杂的mapper在上面处理configuration节点的最后一句mapperElement(root.evalNode("mappers"));。这个是配置文件里面最复杂的,所以再处理上面Mybatis多写了两个类专门处理mapper数据XMLMapperBuilderXMLStatementBuilder

  • XMLMapperBuilder 处理mapper文件里面的resultMap,parameterMap,sql,cache等数据的解析
  • XMLStatementBuilder 处理<select|insert|update|delete>这四类sql语句的处理

先来看看XMLMapperBuilder解析mapper表层文件

private void configurationElement(XNode context) {
    try {
        //拿到namespace。也就是类名
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //将当前namespace设置为当前builderAssistant的命名空间
      builderAssistant.setCurrentNamespace(namespace);
      //处理cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //处理cache
      cacheElement(context.evalNode("cache"));
        /**
         * 注册参数map
         * <parameterMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author">
         * <parameter property="id" />
         * </parameterMap>
         */
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        /**
         * 最终将resultMap转换成ResultMap对象。并将ResultMap对象放到configuration对象里面
         * <resultMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author">
         * <id column="id" property="id" />
         * <result property="username" column="username" />
         * </resultMap>
         */
      resultMapElements(context.evalNodes("/mapper/resultMap"));

      //我们可以使用通用的sql注入到别的sql里面,这些sql放到sqlFragments这个map里面。这步比较简单
      sqlElement(context.evalNodes("/mapper/sql"));

      //处理sql  sql语句比较复杂,所以使用单独的类XMLStatementBuilder类来出来
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

如果你已经按照前面分析配置文件的过程一路走了一遍,那么处理parameterMap和resultMap就很简单了,就是那个节点内容区出里面的各种属性,最终注册到对应的类上面。注册parameterMap使用类ParameterMap和ParameterMapping细节就不一一描述了。再看一下resultMap

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        //<resultMap id="blogWithPosts" type="Blog"> 获得id
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());
        // 获得type
        String type = resultMapNode.getStringAttribute("type",
                resultMapNode.getStringAttribute("ofType",
                        resultMapNode.getStringAttribute("resultType",
                                resultMapNode.getStringAttribute("javaType"))));
        // 一般没有设置,为空
        String extend = resultMapNode.getStringAttribute("extends");
        // 一般没有设置,为空
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        //这里首先去typeAlias中看有没有别名,没有才回通过反射生成class对象
        Class<?> typeClass = resolveClass(type);
        Discriminator discriminator = null;
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        // 第一次additionalResultMappings为空
        resultMappings.addAll(additionalResultMappings);
        //<results>节点下的<result>节点。 <result property="username" column="username" />
        List<XNode> resultChildren = resultMapNode.getChildren();
        //遍历results下面所有的result节点
        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);
                }
                //最普通的column和property就在这里处理
                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;
        }
    }

处理resultMap节点将每一个result子节点封装成ResultMapping对象。然后将resultMapping对象和赋值给MapperBuilderAssistant。下面重点分析select,insert等标签处理的

    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);
            }
        }
    }

因为sql处理比较复杂,所以单独创建XMLStatementBuilder来处理sql。上面方法中使用for循环list表示多个sql语句会使用多个XMLStatementBuilder来创建每个sql语句。

    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");
        //从TypeAliasRegistry里面获得对象,如果不是别名,则通过classUtil创建一个class对象
        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)
        //这个是sql语句处理的地方,最终将sql语句包装成了BoundSql对象。
        /**
         * 对象中包含下面属性
         *   private String sql;  sql语句
         *   private List<ParameterMapping> parameterMappings;  参数map
         *   private Object parameterObject;
         *   private Map<String, Object> additionalParameters;
         *   private MetaObject metaParameters;
         */
        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();
        }

        //上面将<select>里面的所有的数据都取出来,不管有没有,统统调用下面的构造方式,生成一个MappedStatement
        //最终会很规整的构建一个MapperStatement对象,并将这个对象保存到configuration对象里面
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }

上面处理sql语句的方法比较长我们慢慢分析,首先是通过Node获取节点中的属性。一般我们写sql语句属性就三个id;resultMap;parameterType。因为sql语句可以引用<include>标签来引用公共的sql,所以单独有处理这个引入sql的地方

XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

还有使用insert语句处理的时候可以查询主键,所以也有专门处理这个的地方processSelectKeyNodes(id, parameterTypeClass, langDriver);重点是这个方法SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);获得sql对象这个对象包含的主要属性如下:

    private String                 sql;
    private List<ParameterMapping> parameterMappings;
    private Object                 parameterObject;
    private Map<String, Object>    additionalParameters;
    private MetaObject             metaParameters;

分别为sql语句这个sql语句已经将#{}数据转化成了?然后是参数集合List<ParameterMapping>主要是这个两个属性。这个创建调用稍微复杂点调用顺序如下:XMLLanguageDriver.createSqlSource --> XMLScriptBuilder.parseScriptNode --> RawSqlSource构造函数 --> SqlSourceBuilder.parse -->

    //SqlSourceBuilder 类的方法
    public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
    }

看上面的处理应该知道是替换#{}用?替换,并且将参数转化成ParameterMapping对象。然后通个builderAssistant.addMappedStatement包装成MapperStatement对象,并且保存到configuration里面configuration.addMappedStatement(statement);回到XMLScriptBuilder.parseScriptNode方法。如果sql是动态sql也就是包含<#if>这种数据则会走动态sql

    public SqlSource parseScriptNode() {
        List<SqlNode> contents = parseDynamicTags(context);
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        if (isDynamic) {
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode); //动态sql走这个处理器
        } else {
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }

最后将namespace对应的class对应到configuration对象中这样整个mapper处理就完毕了。

相关文章

网友评论

      本文标题:mybatis源码分析-资源加载-下篇

      本文链接:https://www.haomeiwen.com/subject/elzykqtx.html