美文网首页
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