mybatismapper映射解析

Posted yanhui007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatismapper映射解析相关的知识,希望对你有一定的参考价值。

前边在初始化SqlSessionFactory的时候,mapper映射的部分留了下来,现在来完成mapper映射的解析。

mapper映射就是根据xml中mappers标签,找到相应的mapper接口和mapper.xml生成代理类到spring容器。

常用的配置有三种情况:
1、接口信息进行配置:这种方式必须保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。因为是通过获取mapper中的class属性,拼接上.xml来读取UserMapper.xml,如果xml文件名不同或者不在同一个包中是无法读取到xml的。

<mappers>
    <mapper class="org.mybatis.mappers.UserMapper"/>
    <mapper class="org.mybatis.mappers.ProductMapper"/>
    <mapper class="org.mybatis.mappers.ManagerMapper"/>
</mappers>

2、相对路径进行配置:这种方式不用保证同接口同包同名。但是要保证xml中的namespase和对应的接口名相同。

<mappers>
    <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
    <mapper resource="org/mybatis/mappers/ProductMapper.xml"/>
    <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>
</mappers>

3、接口所在包进行配置:这种方式和第一种方式要求一致,保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。

<mappers>
    <package name="org.mybatis.mappers"/>
</mappers>
//    XMLConfigBuilder
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                //包扫描的形式
                if ("package".equals(child.getName())) {
                    // 获取 <package> 节点中的 name 属性
                    String mapperPackage = child.getStringAttribute("name");
                    // 从指定包中查找 所有的 mapper 接口,并根据 mapper 接口解析映射配置
                    configuration.addMappers(mapperPackage);
                } else {
                    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 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 mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url == null && mapperClass != null) {
                        // 通过 mapperClass 解析映射配置
                        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.");
                    }
                }
            }
        }
    }

上面代码对应上面几种方式的入口。
先来看以packae扫描的形式

//Configuration
    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }
    //MapperRegistry
    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        Iterator i$ = mapperSet.iterator();

        while(i$.hasNext()) {
            Class<?> mapperClass = (Class)i$.next();
            //找出包下的所有mapper,挨个调用这个方法
            //入口
            this.addMapper(mapperClass);
        }

    }
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {//mapper是接口
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();//入口
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }
    //mapper代理工厂map 
   private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
//MapperAnnotationBuilder
public void parse() {
        String resource = this.type.toString();
        if (!this.configuration.isResourceLoaded(resource)) {
            this.loadXmlResource();//入口
            this.configuration.addLoadedResource(resource);
            this.assistant.setCurrentNamespace(this.type.getName());
            this.parseCache();
            this.parseCacheRef();
            Method[] methods = this.type.getMethods();
            Method[] var3 = methods;
            int var4 = methods.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Method method = var3[var5];

                try {
                    if (!method.isBridge()) {
                        this.parseStatement(method);
                    }
                } catch (IncompleteElementException var8) {
                    this.configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }

        this.parsePendingMethods();
    }
private void loadXmlResource() {
        if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
            String xmlResource = this.type.getName().replace(‘.‘, ‘/‘) + ".xml";
            InputStream inputStream = null;

            try {
                inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
            } catch (IOException var4) {
            }

            if (inputStream != null) {
                XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
                xmlParser.parse();//入口
            }
        }

    }
//XMLMapperBuilder
public void parse() {
    // 检测映射文件是否已经被解析过
    if (!configuration.isResourceLoaded(resource)) {
        //解析mapper的方法入口
        //入口
        configurationElement(parser.evalNode("/mapper"));
        // 添加资源路径到“已解析资源集合”中
        configuration.addLoadedResource(resource);
        // 通过命名空间绑定 Mapper 接口
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

在 MyBatis 映射文件中,可以配置多种节点。比如 以及 <select | insert | update | delete> 等。下面我们来看一个映射文件配置示例。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.EmployeeMapper">
    <cache/>
    
    <resultMap id="baseMap" type="entity.Employee">
        <result property="id" column="id" jdbcType="INTEGER"></result>
        <result property="name" column="name" jdbcType="VARCHAR"></result>
    </resultMap>
    
    <sql id="table">
        employee
    </sql>
    
    <select id="getAll" resultMap="baseMap">
        select * from  <include refid="table"/>  WHERE id = #{id}
    </select>
    
    <!-- <insert|update|delete/> -->
</mapper>
//XMLMapperBuilder
private void configurationElement(XNode context) {
        try {
            // 获取 mapper 命名空间,如 mapper.EmployeeMapper
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper‘s namespace cannot be empty");
            }

            // 设置命名空间到 builderAssistant 中
            builderAssistant.setCurrentNamespace(namespace);

            // 解析 <cache-ref> 节点
            cacheRefElement(context.evalNode("cache-ref"));

            // 解析 <cache> 节点
            cacheElement(context.evalNode("cache"));

            // 已废弃配置,这里不做分析
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));

            // 解析 <resultMap> 节点
            resultMapElements(context.evalNodes("/mapper/resultMap"));

            // 解析 <sql> 节点
            sqlElement(context.evalNodes("/mapper/sql"));

            // 解析 <select>、<insert>、<update>、<delete> 节点
            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);
        }
    }

接下来就是解析mapper里的具体标签。

解析 cache 节点

MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显示配置才能开启。如下:

<cache/>

也可以使用第三方缓存

<cache type="org.mybatis.caches.redis.RedisCache"/>

其中有一些属性可以选择

<cache eviction="LRU"  flushInterval="60000"  size="512" readOnly="true"/>
private void cacheElement(XNode context) throws Exception {
        if (context != null) {
            // 获取type属性,如果type没有指定就用默认的PERPETUAL(早已经注册过的别名的PerpetualCache)
            String type = context.getStringAttribute("type", "PERPETUAL");
            // 根据type从早已经注册的别名中获取对应的Class,PERPETUAL对应的Class是PerpetualCache.class
            // 如果我们写了type属性,如type="org.mybatis.caches.redis.RedisCache",这里将会得到RedisCache.class
            Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
            //获取淘汰方式,默认为LRU(早已经注册过的别名的LruCache),最近最少使用到的先淘汰
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
            Long flushInterval = context.getLongAttribute("flushInterval");
            Integer size = context.getIntAttribute("size");
            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
            boolean blocking = context.getBooleanAttribute("blocking", false);

            // 获取子节点配置
            Properties props = context.getChildrenAsProperties();

            // 构建缓存对象
            //入口
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }
public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,Long flushInterval,
                             Integer size,boolean readWrite,boolean blocking,Properties props) {

        // 使用建造模式构建缓存实例
        Cache cache = new CacheBuilder(currentNamespace)
                .implementation(valueOrDefault(typeClass, PerpetualCache.class))
                .addDecorator(valueOrDefault(evictionClass, LruCache.class))
                .clearInterval(flushInterval)
                .size(size)
                .readWrite(readWrite)
                .blocking(blocking)
                .properties(props)
                .build();

        // 添加缓存到 Configuration 对象中
        configuration.addCache(cache);

        // 设置 currentCache 属性,即当前使用的缓存
        currentCache = cache;
        return cache;
    }
public Cache build() {
        // 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
        setDefaultImplementations();//入口1

        // 通过反射创建缓存
        //入口2
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // 仅对内置缓存 PerpetualCache 应用装饰器
        if (PerpetualCache.class.equals(cache.getClass())) {
            // 遍历装饰器集合,应用装饰器
            for (Class<? extends Cache> decorator : decorators) {
                // 通过反射创建装饰器实例
                cache = newCacheDecoratorInstance(decorator, cache);
                // 设置属性值到缓存实例中
                setCacheProperties(cache);
            }
            // 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            // 应用具有日志功能的缓存装饰器
            cache = new LoggingCache(cache);
        }
        return cache;
    }
private void setDefaultImplementations() {
        if (this.implementation == null) {
            //设置默认缓存类型为PerpetualCache
            this.implementation = PerpetualCache.class;
            if (this.decorators.isEmpty()) {
                this.decorators.add(LruCache.class);
            }
        }
    }
    private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
        //获取构造器
        Constructor cacheConstructor = this.getBaseCacheConstructor(cacheClass);

        try {
            //通过构造器实例化Cache
            return (Cache)cacheConstructor.newInstance(id);
        } catch (Exception var5) {
            throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + var5, var5);
        }
    }

解析 resultMap 节点

private void resultMapElements(List<XNode> list) throws Exception {
        // 遍历 <resultMap> 节点列表
        for (XNode resultMapNode : list) {
            try {
                // 解析 resultMap 节点
                //入口
                resultMapElement(resultMapNode);
            } catch (IncompleteElementException e) {
            }
        }
    }
    private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
        return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList());
    }
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());

        // 获取 id 和 type 属性
        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
        String type = resultMapNode.getStringAttribute("type",
                resultMapNode.getStringAttribute("ofType",
                        resultMapNode.getStringAttribute("resultType",
                                resultMapNode.getStringAttribute("javaType"))));
        // 获取 extends 和 autoMapping
        String extend = resultMapNode.getStringAttribute("extends");
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

        // 获取 type 属性对应的类型
        Class<?> typeClass = resolveClass(type);
        Discriminator discriminator = null;
        //创建ResultMapping集合,对应resultMap子节点的id和result节点
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        resultMappings.addAll(additionalResultMappings);

        // 获取并遍历 <resultMap> 的子节点列表
        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())) {
                    // 添加 ID 到 flags 集合中
                    flags.add(ResultFlag.ID);
                }
                // 解析 id 和 result 节点,将id或result节点生成相应的 ResultMapping,将ResultMapping添加到resultMappings集合中
                //入口
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
            }
        }
        //创建ResultMapResolver对象
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend,
                discriminator, resultMappings, autoMapping);
        try {
            // 根据前面获取到的信息构建 ResultMap 对象
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
    }

节点中,子节点 都是常规配置,比较常见。我们来看看解析过程

 private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
        String property;
        // 根据节点类型获取 name 或 property 属性
        if (flags.contains(ResultFlag.CONSTRUCTOR)) {
            property = context.getStringAttribute("name");
        } else {
            property = context.getStringAttribute("property");
        }

        // 获取其他各种属性
        String column = context.getStringAttribute("column");
        String javaType = context.getStringAttribute("javaType");
        String jdbcType = context.getStringAttribute("jdbcType");
        String nestedSelect = context.getStringAttribute("select");

        /*
         * 解析 resultMap 属性,该属性出现在 <association> 和 <collection> 节点中。
         * 若这两个节点不包含 resultMap 属性,则调用 processNestedResultMappings 方法,递归调用resultMapElement解析<association> 和 <collection>的嵌套节点,生成resultMap,并返回resultMap.getId();
         * 如果包含resultMap属性,则直接获取其属性值,这个属性值对应一个resultMap节点
         */
        //入口
        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);
        Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);

        // 构建 ResultMapping 对象
        //入口2
        return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
                nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
    }


private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
        if (("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) && context.getStringAttribute("select") == null) {
            ResultMap resultMap = this.resultMapElement(context, resultMappings);
            return resultMap.getId();
        } else {
            return null;
        }
    }

只要此节点是(association或者collection)并且select为空,就说明是嵌套查询,那如果select不为空呢?那说明是延迟加载此节点的信息,并不属于嵌套查询,但是有可能有多个association或者collection,有一个设置为延迟加载也就是select属性不为空,有一个没有设置延迟加载,那说明resultMap中有嵌套查询的ResultMapping,也有延迟加载的ResultMapping,这个在后面结果集映射时会用到。

下面以 节点为例,演示该节点的两种配置方式,分别如下:

第一种配置方式是通过 resultMap 属性引用其他的 节点,配置如下:

<resultMap id="articleResult" type="Article">
    <id property="id" column="id"/>
    <result property="title" column="article_title"/>
    <!-- 引用 authorResult,此时为嵌套查询 -->
    <association property="article_author" column="article_author_id" javaType="Author" resultMap="authorResult"/>
    <!-- 引用 authorResult,此时为延迟查询 -->
    <association property="article_author" column="article_author_id" javaType="Author" select="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
    <id property="id" column="author_id"/>
    <result property="name" column="author_name"/>
</resultMap>

第二种配置方式是采取 resultMap 嵌套的方式进行配置,如下:

<resultMap id="articleResult" type="Article">
    <id property="id" column="id"/>
    <result property="title" column="article_title"/>
    <!-- resultMap 嵌套 -->
    <association property="article_author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="name" column="author_name"/>
    </association>
</resultMap>

第二种配置, 的子节点是一些结果映射配置,这些结果配置最终也会被解析成 ResultMap。

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) {

        // resultType:即 <resultMap type="xxx"/> 中的 type 属性
        // property:即 <result property="xxx"/> 中的 property 属性
        //入口1
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);

        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

        List<ResultMapping> composites = parseCompositeColumnName(column);

        // 通过建造模式构建 ResultMapping
        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();//入口2
    }

    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
        if (javaType == null && property != null) {
            try {
                //获取ResultMap中的type属性的元类,如<resultMap id="user" type="java.model.User"/> 中User的元类
                MetaClass metaResultType = MetaClass.forClass(resultType, this.configuration.getReflectorFactory());
                //<result property="name" javaType="String"/>,如果result中没有设置javaType,则获取元类属性对那个的类型
                javaType = metaResultType.getSetterType(property);
            } catch (Exception var5) {
                ;
            }
        }

        if (javaType == null) {
            javaType = Object.class;
        }

        return javaType;
    }

    public ResultMapping build() {
        resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
        resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
        resolveTypeHandler();
        validate();
        return resultMapping;
    }

public class ResultMapping {
    private Configuration configuration;
    private String property;
    private String column;
    private Class<?> javaType;
    private JdbcType jdbcType;
    private TypeHandler<?> typeHandler;
    private String nestedResultMapId;
    private String nestedQueryId;
    private Set<String> notNullColumns;
    private String columnPrefix;
    private List<ResultFlag> flags;
    private List<ResultMapping> composites;
    private String resultSet;
    private String foreignColumn;
    private boolean lazy;

    ResultMapping() {
    }
}

我们看到ResultMapping中有属性nestedResultMapId表示嵌套查询和nestedQueryId表示延迟查询

ResultMapping就是和ResultMap中子节点id和result对应

<id column="wi_id" jdbcType="INTEGER"  property="id" />
<result column="warrant_no" jdbcType="String"  jdbcType="CHAR" property="warrantNo" />

前面的分析我们知道了 等节点最终都被解析成了 ResultMapping。并且封装到了resultMappings集合中,紧接着要做的事情是构建 ResultMap,关键代码在resultMapResolver.resolve():

 public ResultMap resolve() {
        return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
    }

    public ResultMap addResultMap(
            String id, Class<?> type, String extend, Discriminator discriminator,
            List<ResultMapping> resultMappings, Boolean autoMapping) {

        // 为 ResultMap 的 id 和 extend 属性值拼接命名空间
        id = applyCurrentNamespace(id, false);
        extend = applyCurrentNamespace(extend, true);

        if (extend != null) {
            if (!configuration.hasResultMap(extend)) {
                throw new IncompleteElementException("Could not find a parent resultmap with id ‘" + extend + "‘");
            }
            ResultMap resultMap = configuration.getResultMap(extend);
            List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
            extendedResultMappings.removeAll(resultMappings);

            boolean declaresConstructor = false;
            for (ResultMapping resultMapping : resultMappings) {
                if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    declaresConstructor = true;
                    break;
                }
            }

            if (declaresConstructor) {
                Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
                while (extendedResultMappingsIter.hasNext()) {
                    if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                        extendedResultMappingsIter.remove();
                    }
                }
            }
            resultMappings.addAll(extendedResultMappings);
        }

        // 构建 ResultMap
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
                .discriminator(discriminator)
                .build();
        // 将创建好的ResultMap加入configuration中
        configuration.addResultMap(resultMap);
        return resultMap;
    }

我们先看看ResultMap

public class ResultMap {
    private String id;
    private Class<?> type;
    private List<ResultMapping> resultMappings;
    //用于存储 <id> 节点对应的 ResultMapping 对象
    private List<ResultMapping> idResultMappings;
    private List<ResultMapping> constructorResultMappings;
    //用于存储 <id> 和 <result> 节点对应的 ResultMapping 对象
    private List<ResultMapping> propertyResultMappings;
    //用于存储 所有<id>、<result> 节点 column 属性
    private Set<String> mappedColumns;
    private Discriminator discriminator;
    private boolean hasNestedResultMaps;
    private boolean hasNestedQueries;
    private Boolean autoMapping;

    private ResultMap() {
    }
    //略
}

再来看看通过建造模式构建 ResultMap 实例

public ResultMap build() {
    if (resultMap.id == null) {
        throw new IllegalArgumentException("ResultMaps must have an id");
    }
    resultMap.mappedColumns = new HashSet<String>();
    resultMap.mappedProperties = new HashSet<String>();
    resultMap.idResultMappings = new ArrayList<ResultMapping>();
    resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
    resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
    final List<String> constructorArgNames = new ArrayList<String>();

    for (ResultMapping resultMapping : resultMap.resultMappings) {
        /*
         * resultMapping.getNestedQueryId()不为空,表示当前resultMap是中有需要延迟查询的属性
         * resultMapping.getNestedResultMapId()不为空,表示当前resultMap是一个嵌套查询
         * 有可能当前ResultMapp既是一个嵌套查询,又存在延迟查询的属性
         */
        resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
        resultMap.hasNestedResultMaps =  resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);

        final String column = resultMapping.getColumn();
        if (column != null) {
            // 将 colum 转换成大写,并添加到 mappedColumns 集合中
            resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        } else if (resultMapping.isCompositeResult()) {
            for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
                final String compositeColumn = compositeResultMapping.getColumn();
                if (compositeColumn != null) {
                    resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
                }
            }
        }

        // 添加属性 property 到 mappedProperties 集合中
        final String property = resultMapping.getProperty();
        if (property != null) {
            resultMap.mappedProperties.add(property);
        }

        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            resultMap.constructorResultMappings.add(resultMapping);
            if (resultMapping.getProperty() != null) {
                constructorArgNames.add(resultMapping.getProperty());
            }
        } else {
            // 添加 resultMapping 到 propertyResultMappings 中
            resultMap.propertyResultMappings.add(resultMapping);
        }

        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
            // 添加 resultMapping 到 idResultMappings 中
            resultMap.idResultMappings.add(resultMapping);
        }
    }
    if (resultMap.idResultMappings.isEmpty()) {
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
    }
    if (!constructorArgNames.isEmpty()) {
        final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
        if (actualArgNames == null) {
            throw new BuilderException("Error in result map ‘" + resultMap.id
                + "‘. Failed to find a constructor in ‘"
                + resultMap.getType().getName() + "‘ by arg names " + constructorArgNames
                + ". There might be more info in debug log.");
        }
        Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() {
            @Override
            public int compare(ResultMapping o1, ResultMapping o2) {
                int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
                int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
                return paramIdx1 - paramIdx2;
            }
        });
    }

    // 将以下这些集合变为不可修改集合
    resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
    resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
    resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
    resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
    resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
    return resultMap;
}

主要做的事情就是将 ResultMapping 实例及属性分别存储到不同的集合中。

解析 sql 节点

节点用来定义一些可重用的 SQL 语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过 节点引用 节点定义的内容。

<sql id="table">
    user
</sql>

<select id="findOne" resultType="Article">
    SELECT * FROM <include refid="table"/> WHERE id = #{id}
</select>
private void sqlElement(List<XNode> list) throws Exception {
    if (configuration.getDatabaseId() != null) {
        // 调用 sqlElement 解析 <sql> 节点
        sqlElement(list, configuration.getDatabaseId());
    }

    // 再次调用 sqlElement,不同的是,这次调用,该方法的第二个参数为 null
    sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
        // 获取 id 和 databaseId 属性
        String databaseId = context.getStringAttribute("databaseId");
        String id = context.getStringAttribute("id");

        // id = currentNamespace + "." + id
        id = builderAssistant.applyCurrentNamespace(id, false);

        // 检测当前 databaseId 和 requiredDatabaseId 是否一致
        if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            // 将 <id, XNode> 键值对缓存到XMLMapperBuilder对象的 sqlFragments 属性中,以供后面的sql语句使用
            sqlFragments.put(id, context);
        }
    }
}

解析select|insert|update|delete节点

节点,节点类型:ELEMENT_NODE,此时会进入第二个分支,获取到获取 节点中的当前节点替换成 节点,然后调用toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);,将 中的内容插入到 节点之前,也就是将user插入到 节点之前,现在不需要 节点了,最后将该节点从 dom 中移除

创建SqlSource
创建SqlSource在createSqlSource方法中

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
}

// -☆- XMLScriptBuilder
public SqlSource parseScriptNode() {
    // 解析 SQL 语句节点
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    // 根据 isDynamic 状态创建不同的 SqlSource
    if (isDynamic) {
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}
/** 该方法用于初始化 nodeHandlerMap 集合,该集合后面会用到 */
private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}
    
protected MixedSqlNode 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));
        //如果节点是TEXT_NODE类型
        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
                isDynamic = true;
            } else {
                // 创建 StaticTextSqlNode
                contents.add(new StaticTextSqlNode(data));
            }

        // child 节点是 ELEMENT_NODE 类型,比如 <if>、<where> 等
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
            // 获取节点名称,比如 if、where、trim 等
            String nodeName = child.getNode().getNodeName();
            // 根据节点名称获取 NodeHandler,也就是上面注册的nodeHandlerMap
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // 处理 child 节点,生成相应的 SqlNode
            handler.handleNode(child, contents);

            // 设置 isDynamic 为 true
            isDynamic = true;
        }
    }
    return new MixedSqlNode(contents);
}

对于if、trim、where等这些动态节点,是通过对应的handler来解析的,如下

handler.handleNode(child, contents);

该代码用于处理动态 SQL 节点,并生成相应的 SqlNode。下面来简单分析一下 WhereHandler 的代码。

/** 定义在 XMLScriptBuilder 中 */
private class WhereHandler implements NodeHandler {

    public WhereHandler() {
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 调用 parseDynamicTags 解析 <where> 节点
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // 创建 WhereSqlNode
        WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
        // 添加到 targetContents
        targetContents.add(where);
    }
}

我们已经将 XML 配置解析了 SqlSource,下面我们看看MappedStatement的构建。

构建MappedStatement

SQL 语句节点可以定义很多属性,这些属性和属性值最终存储在 MappedStatement 中。

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");
    }
  // 拼接上命名空间,如 <select id="findOne" resultType="User">,则id=java.mybaits.dao.UserMapper.findOne
    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))
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .resultSetType(resultSetType).useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);//这里用到了前面解析<cache>节点时创建的Cache对象,设置到MappedStatement对象里面的cache属性中

    // 获取或创建 ParameterMap
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }

    // 构建 MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 添加 MappedStatement 到 configuration 的 mappedStatements 集合中
    // 通过UserMapper代理对象调用findOne方法时,就可以拼接UserMapper接口名java.mybaits.dao.UserMapper和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,然后执行对应的sql语句
    configuration.addMappedStatement(statement);
    return statement;
}

这里我们要注意,MappedStatement对象中有一个cache属性,将前面解析节点时创建的Cache对象,设置到MappedStatement对象里面的cache属性中,以备后面二级缓存使用,我们后面专门来讲这一块。

我们还要注意一个地方,.resultMaps(getStatementResultMaps(resultMap, resultType, id)),设置MappedStatement的resultMaps,我们来看看是怎么获取resultMap的

private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
    //拼接上当前nameSpace
    resultMap = this.applyCurrentNamespace(resultMap, true);
    //创建一个集合
    List<ResultMap> resultMaps = new ArrayList();
    if (resultMap != null) {
        //通过,分隔字符串,一般resultMap只会是一个,不会使用逗号
        String[] resultMapNames = resultMap.split(",");
        String[] arr$ = resultMapNames;
        int len$ = resultMapNames.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            String resultMapName = arr$[i$];

            try {
                //从configuration中通过resultMapName获取ResultMap对象加入到resultMaps中
                resultMaps.add(this.configuration.getResultMap(resultMapName.trim()));
            } catch (IllegalArgumentException var11) {
                throw new IncompleteElementException("Could not find result map " + resultMapName, var11);
            }
        }
    } else if (resultType != null) {
        ResultMap inlineResultMap = (new org.apache.ibatis.mapping.ResultMap.Builder(this.configuration, statementId + "-Inline", resultType, new ArrayList(), (Boolean)null)).build();
        resultMaps.add(inlineResultMap);
    }

    return resultMaps;
}

从configuration中获取到ResultMap并设置到MappedStatement中,当查询结束后,就可以拿到ResultMap进行结果映射,这个在后面讲

Mapper 接口绑定

映射文件解析完成后,我们需要通过命名空间将绑定 mapper 接口,看看具体绑定的啥
private void bindMapperForNamespace() {
// 获取映射文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 根据命名空间解析 mapper 类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {
// 检测当前 mapper 类是否被绑定过
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
// 绑定 mapper 类
configuration.addMapper(boundType);
}
}
}
}

// Configuration
public void addMapper(Class type) {
// 通过 MapperRegistry 绑定 mapper 类
mapperRegistry.addMapper(type);
}

// MapperRegistry
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/*
* 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
*/
knownMappers.put(type, new MapperProxyFactory(type));

        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析注解中的信息
        parser.parse();
        loadCompleted = true;
    } finally {
        if (!loadCompleted) {
            knownMappers.remove(type);
        }
    }
}

}

其实就是获取当前映射文件的命名空间,并获取其Class,也就是获取每个Mapper接口,然后为每个Mapper接口创建一个代理类工厂,new MapperProxyFactory(type),并放进 knownMappers 这个HashMap中,我们来看看这个MapperProxyFactory

public class MapperProxyFactory<T> {
    //存放Mapper接口Class
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
        //生成mapperInterface的代理类
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

以上是关于mybatismapper映射解析的主要内容,如果未能解决你的问题,请参考以下文章

java解析xml的几种方式哪种最好?

片段(Java) | 机试题+算法思路+考点+代码解析 2023

EF添加关联的提示问题:映射从第 260 行开始的片段时有问题:

Python代码阅读(第26篇):将列表映射成字典

无法解析片段中的 findViewById [重复]

无法解析片段中的 ViewModelProvider 构造?