【Mybatis】Mybatis源码之mappers标签解析

561 阅读6分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

时序图

sequenceDiagram
participant A as XMLConfigBuilder
participant B as Configuration
participant C as XMLMapperBuilder
participant D as MapperRegistry
participant E as MapperAnnotationBuilder

A ->> A: mapperElement
alt 子标签是package
A ->> B: addMappers
B ->> D: addMappers
D ->> D: addMapper
D ->> E: parse
E ->> E: parseStatement

else 子标签是mapper

alt 解析resource属性 
A ->> C : parse
C ->> C : configurationElement
C ->> C : bindMapperForNamespace
C ->> B : addMapper
B ->> D : addMapper

else 解析url属性 
A ->> C : parse
else 解析class属性 
A ->> B: addMapper
B ->> D: addMapper
D ->> E: parse
end
end
  • 配置包的方式与配置class的方式执行逻辑相似,这里只跟配置包的方式的代码
  • 配置resource的方式与配置url的方式执行逻辑一直,这里只跟配置resource的方式的代码

步骤详解

  • mapperElement方法
/**
 * 解析mappers节点,例如:
 * <mappers>
 *    <mapper resource="resources/xml/PurchaseMapper.xml"/>
 *    <package name="org.apache.ibatis.z_run.mapper"/>
 * </mappers>
 * @param parent mappers节点
 * @throws Exception
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 处理mappers的子节点,即mapper节点或者package节点
            if ("package".equals(child.getName())) { // package节点
                // 取出包路径
                String mapperPackage = child.getStringAttribute("name");
                // 获取包下所有的Mapper接口,解析对应的XML,全部加入Mappers中
                configuration.addMappers(mapperPackage);
            } else {
                // resource、url、class这三个属性只有一个生效
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    ErrorContext.instance().resource(resource);
                    // 获取文件的输入流
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 使用XMLMapperBuilder解析映射文件
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    ErrorContext.instance().resource(url);
                    // 从网络获得输入流
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    // 使用XMLMapperBuilder解析映射文件
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    // 加载配置的映射接口,获取对应的XML,加入到Mappers中
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

配置包的方式

Configuration#addMappers

// 映射注册表
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

public void addMappers(String packageName) {
    /**
     * 这个mapperRegistry是在Configuration对象中创建的,创建时将当前Configuration对象传进去了
     * 因此后续步骤的操作,都是操作的同一个Configuration对象,也就是当前对象
     */
    mapperRegistry.addMappers(packageName);
}

MapperRegistry#addMappers

/**
 * @since 3.2.2
 */
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}

/**
 * @since 3.2.2
 */
/**
 * 扫描包下的所有父类是Object的class文件
 * @param packageName 包名
 * @param superType Object
 */
public void addMappers(String packageName, Class<?> superType) {
    // 获取包中的class文件
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 解析接口,并将解析结果放入Configuration的mappedStatements中
    for (Class<?> mapperClass : mapperSet) {
        addMapper(mapperClass);
    }
}

public <T> void addMapper(Class<T> type) {
    // 只处理接口类型的Class对象
    if (type.isInterface()) {
        // 判断当前接口是否已经解析
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            // 将当前接口放入knownMappers,防止重复解析
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 开始解析
            parser.parse();
            loadCompleted = true;
        } finally {
            // 解析失败后,从knownMappers中移除当前接口
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

MapperAnnotationBuilder#parse

/**
 * 解析包含注解的接口文档
 */
public void parse() {
    String resource = type.toString();
    // 防止重复分析
    if (!configuration.isResourceLoaded(resource)) {
        // 寻找类名称对应的 resource路径下是否有 xml 配置,如果有则直接解析掉,这样就支持注解和xml一起混合使用了
        loadXmlResource();
        // 记录资源路径
        configuration.addLoadedResource(resource);
        // 设置命名空间
        assistant.setCurrentNamespace(type.getName());
        // 处理缓存
        // 解析CacheNamespace注解
        parseCache();
        // 解析CacheNamespaceRef注解
        parseCacheRef();
        // 获取接口中的所有方法
        Method[] methods = type.getMethods();
        // 遍历解析
        for (Method method : methods) {
            try {
                // 排除桥接方法,桥接方法是为了匹配泛型的类型擦除而由编译器自动引入的,并非用户编写的方法,因此要排除掉。
                // issue #237
                if (!method.isBridge()) {
                    // 解析该方法
                    parseStatement(method);
                }
            } catch (IncompleteElementException e) {
                // 异常方法暂存起来
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    // 处理异常的方法
    parsePendingMethods();
}

MapperAnnotationBuilder#parseStatement

  • 只能解析注解,不能解析XML
/**
 * 解析该方法,主要是解析方法上的注解信息
 * @param method
 */
void parseStatement(Method method) {
    // 通过字方法获取参数类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 获取方法的脚本语言渠道
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 通过注解获取SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
        // 获取方法上可能存在的配置信息,配置信息由@Options注解指定
        Options options = method.getAnnotation(Options.class);
        final String mappedStatementId = type.getName() + "." + method.getName();
        Integer fetchSize = null;
        Integer timeout = null;
        StatementType statementType = StatementType.PREPARED;
        ResultSetType resultSetType = configuration.getDefaultResultSetType();
        SqlCommandType sqlCommandType = getSqlCommandType(method);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = !isSelect;
        boolean useCache = isSelect;

        KeyGenerator keyGenerator;
        String keyProperty = null;
        String keyColumn = null;
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // first check for SelectKey annotation - that overrides everything else
            SelectKey selectKey = method.getAnnotation(SelectKey.class);
            if (selectKey != null) {
                keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
                keyProperty = selectKey.keyProperty();
            } else if (options == null) {
                keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            } else {
                keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
                keyProperty = options.keyProperty();
                keyColumn = options.keyColumn();
            }
        } else {
            keyGenerator = NoKeyGenerator.INSTANCE;
        }

        if (options != null) {
            if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
                flushCache = true;
            } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
                flushCache = false;
            }
            useCache = options.useCache();
            fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
            timeout = options.timeout() > -1 ? options.timeout() : null;
            statementType = options.statementType();
            if (options.resultSetType() != ResultSetType.DEFAULT) {
                resultSetType = options.resultSetType();
            }
        }

        String resultMapId = null;
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
            resultMapId = String.join(",", resultMapAnnotation.value());
        } else if (isSelect) {
            resultMapId = parseResultMap(method);
        }

        // 将获取到的信息存入 configuration
        assistant.addMappedStatement(
                mappedStatementId,
                sqlSource,
                statementType,
                sqlCommandType,
                fetchSize,
                timeout,
                // ParameterMapID
                null,
                parameterTypeClass,
                resultMapId,
                getReturnType(method),
                resultSetType,
                flushCache,
                useCache,
                // TODO gcode issue #577
                false,
                keyGenerator,
                keyProperty,
                keyColumn,
                // DatabaseID
                null,
                languageDriver,
                // ResultSets
                options != null ? nullOrEmpty(options.resultSets()) : null);
    }
}

配置resource的方式

XMLMapperBuilder#parse

/**
 * 解析映射文件
 */
public void parse() {
    // 该节点是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
        // 处理mapper节点
        configurationElement(parser.evalNode("/mapper"));
        // 加入已解析的列表,防止重复解析
        configuration.addLoadedResource(resource);
        // 将Mapper注册给configuration
        bindMapperForNamespace();
    }

    // 下面分别用来处理失败的<resultMap>、<cache-ref>、SQL语句
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

XMLMapperBuilder#configurationElement

  • 只能解析XML
/**
 * 解析映射文件的下层节点
 * @param context 映射文件根节点
 */
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");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // 映射文件中其他配置节点的解析
        // 解析缓存标签
        cacheRefElement(context.evalNode("cache-ref"));
        cacheElement(context.evalNode("cache"));
        // 解析参数映射
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 解析结果集映射
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // 解析sql标签
        sqlElement(context.evalNodes("/mapper/sql"));
        // 处理各个数据库操作语句
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

XMLMapperBuilder#buildStatementFromContext

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

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

XMLStatementBuilder#parseStatementNode

/**
 * 解析select、insert、update、delete这四类节点
 */
public void parseStatementNode() {
    // 读取当前节点的id与databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    // 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
    }

    // 读取节点名称
    String nodeName = context.getNode().getNodeName();
    // 读取和判断语句类型SqlCommandType
    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节点
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 参数类型
    String parameterType = context.getStringAttribute("parameterType");
    /**
     * 1. 根据属性值去别名映射器中查找对应的类
     * 2. 如果别名映射器中没有,则创建此类名对应的Class对象
     */
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 语句类型,默认值是XMLLanguageDriver
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 此时,<selectKey> 和 <include> 节点均已被解析完毕并被删除,开始进行SQL解析
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 判断是否已经有解析好的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // 全局或者本语句只要启用自动key生成,则使用key生成
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 根据获取的LanguageDriver来创建SqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 读取各个配置属性
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
        resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    // 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

XMLMapperBuilder#bindMapperForNamespace

private void bindMapperForNamespace() {
    // 获取当前命名空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
        Class<?> boundType = null;
        try {
            // 根据命名空间获取对应的Class对象
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
        }
        if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {
                // Spring may not know the real resource name so we set a flag
                // to prevent loading again this resource from the mapper interface
                // look at MapperAnnotationBuilder#loadXmlResource
                // 当前xml文件已经解析
                configuration.addLoadedResource("namespace:" + namespace);
                // 设置当前xml对应的Class对象到Configuration中,并对当前xml对应的接口中是否含有注解进行解析
                configuration.addMapper(boundType);
            }
        }
    }
}

Configuration#addMapper

public <T> void addMapper(Class<T> type) {
    // 将当前接口添加到映射关系中
    mapperRegistry.addMapper(type);
}

MapperRegistry#addMapper

public <T> void addMapper(Class<T> type) {
    // 只处理接口类型的Class对象
    if (type.isInterface()) {
        // 判断当前接口是否已经解析
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            /**
             * 1. 将当前接口放入knownMappers,防止重复解析
             * 2. 将接口使用MapperProxyFactory包装后,放入映射关系中
             */
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            // 解析注解的解析器
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 开始解析
            parser.parse();
            loadCompleted = true;
        } finally {
            // 解析失败后,从knownMappers中移除当前接口
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

以上便是mappers标签解析的流程,可以看出,使用包名及class方式的配置只能解析基于注解的SqlSource,使用url及resource方式的配置只能解析基于XML的SqlSource。关于参数映射、结果集映射以及SqlSource对象的解析,请看下回分解!