【mybatis源码】2、解析、执行器、缓存、动态代理

本文围绕MyBatis展开,详细解析了xml文件解析过程,包括配置、数据源、mapper元素等。阐述了创建sqlSession、代理及查询的流程,还介绍了插件与拦截器、缓存问题。同时讲解了MyBatis与Spring的整合方式,分析了sql注入、参数处理和事务等内容,梳理了SqlSession的核心组件。

前言:

  • 这个链接中有mybatis的各个对象说明,一定要结合看:https://blog.youkuaiyun.com/hancoder/article/details/111244516

  • 个人源码解析地址:https://blog.youkuaiyun.com/hancoder/article/details/110152732

  • mybatis插件分析:https://blog.youkuaiyun.com/hancoder/article/details/110914534

  • 本文主要按解析xml、获取session、执行sql等分为几个大章节

  • 有的小章节前面加了【】,是代表面试比较常见的问题,比如@Param、分页和sql注入等问题

  • 设计模式的Builder建造者模式、装饰者模式很重要。插件中也用到了代理模式和责任链模式

  • 动态代理模式也得看会才能看这个,最起码invocationHandler和invoke知道怎么回事

一、xml文件解析

  • XMLConfigBuilder是解析根标签Configuration的
  • XMLMapperBuilder是解析根标签Mapper的
SqlSessionFactoryBuilder
  • build()的方法是传入一个配置文件流参数,返回一个SqlSessionFactor实例对象。在这里就是传入configuration配置,得到一个SqlSessionFactory实例
// SqlSessionFactoryBuilder.java

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

// 开始创建sqlSessionFactory
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
   
   
    //调用了重载方法
    return build(inputStream, null, null);
}

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
   
   
    try {
   
   
        //  XMLConfigBuilder是专门解析mybatis的配置文件的类
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //这里又调用了一个重载方法Configuration。parser.parse()的返回值是Configuration对象
        return build(parser.parse());//XMLConfigBuilder的parse()返回一个Configuration对象 
    } catch (Exception e) {
   
   
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } //省略部分代码
}

public SqlSessionFactory build(Configuration config) {
   
   
    return new DefaultSqlSessionFactory(config);
}

总结:
    两个BuilderXMLConfigBuilder是parser
    parser.parse()就是返回Builder包含的Configuration对象
    sqlSessionFactoryBuilder.build(config)返回都是DefaultSqlSessionFactory对象。
XMLConfigBuilder
  • XMLConfigBuilder中的属性configuration在父类BaseBuilder中
  • 通过parse()方法返回一个Configuration实例
// XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
   
   
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()),   environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
   
   
    super(new Configuration());//BaseBuilder
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;// XML里的成员parser = new XPathParser(reader, true, props, new XMLMapperEntityResolver())
}

// 返回Configuration
public Configuration parse() {
   
   
    if (parsed) {
   
   
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;//也就是说这个函数只能进来一次
    parseConfiguration(parser.evalNode("/configuration"));//这里进去把config的属性注入好
    return configuration;
}

mybatis-config.xml元素解析

解析mybatis.xml配置文件中的各个标签。

值得注意的是,先解析好properties,然后就可以把设置注入到configuration中了

//------XMLConfigBuilder---------------
private void parseConfiguration(XNode root) {
   
   //Xnode是一个XML,properties还没注入
    try {
   
   
        // 解析properties
        propertiesElement(root.evalNode("properties")); //issue #117 read properties first // 在这里把配置才拿出来
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));// 解析拦截器插件
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        settingsElement(root.evalNode("settings"));
        environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 //<environments> //函数传入的时候后root.evalNode("environments")整体已经把配置拿到了
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
   
   
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

上面解析的是mybatis.xml配置文件,值得注意的是在解析其中<mappers>标签的时候,会解析指定的Xxxmapper.xml文件

①properties配置解析

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="root"/>
  <property name="password" value="123123"/>
</properties>
propertiesElement

解析properties

//---------------------
//先解析properties把属性设置好 //  propertyConfigurer
private void propertiesElement(XNode context) throws Exception {
   
   
    if (context != null) {
   
   
        Properties defaults = context.getChildrenAsProperties();
        // 配置文件的属性有url或者resource
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
   
   //不能同时指定
            throw new BuilderException("不能同时指定url和res");
        }
        if (resource != null) {
   
   // 如果指定的是resource
            // 得到一个Properties对象,然后放到configuration.variables这个Properties中
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
   
    // 如果指定的是url
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
   
   
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

②解析数据源environment、DataSource

会把解析出来的内容放到configuration 属性中,然后用属性解析数据源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
environmentsElement
//---------------------
// 解析完properties后就能${}拿到了
private void environmentsElement(XNode context) {
   
   //<environments>
    if (context != null) {
   
   
        if (environment == null) {
   
   
            environment = context.getStringAttribute("default");
        }
        // 循环开发环境、测试环境等
        for (XNode child : context.getChildren()) {
   
    // 每个<environment id="development">
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
   
   
                // 事物 mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器
                // <transactionManager type="JDBC"/>
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // <dataSource type="POOLED">
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));//看下面函数
                // 得到数据源
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                // 设置environment给configuration 
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
//---------------------
// 数据源  <dataSource type="POOLED">  <property name="url" value="${url}"/>
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
   
   
    if (context != null) {
   
   
        String type = context.getStringAttribute("type");
        // 解析多个property标签,数据源的${}肯定从这里面替换
        Properties props = context.getChildrenAsProperties();// 获得属性
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();// 获得class对象,然后new
        factory.setProperties(props);//把属性设置到数据源
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

public Properties getChildrenAsProperties() {
   
   
    Properties properties = new Properties();
    // 解析多个property标签
    for (XNode child : getChildren()) {
   
   
        String name = child.getStringAttribute("name");
        String value = child.getStringAttribute("value");
        if (name != null && value != null) {
   
   
            properties.setProperty(name, value);
        }
    }
    return properties;
}
/**
 * 解析typeAliases 节点
 * <typeAliases>
 *     <!--<package name="com.lpf.entity"></package>-->
 *     <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
 * </typeAliases>
 * @param parent
 */
private void typeAliasesElement(XNode parent) {
   
   
    if (parent != null) {
   
   
        for (XNode child : parent.getChildren()) {
   
   
            //如果子节点是package, 那么就获取package节点的name属性, mybatis会扫描指定的package
            if ("package".equals(child.getName())) {
   
   
                String typeAliasPackage = child.getStringAttribute("name");
                //TypeAliasRegistry 负责管理别名, 这儿就是通过TypeAliasRegistry 进行别名注册
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
   
   
                //如果子节点是typeAlias节点,那么就获取alias属性和type的属性值
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
   
   
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
   
   
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
   
   
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
   
   
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}
typeHandlerElement

类型转换器的用处:

  • 在预处理语句(PreparedStatement)中设置参数
  • 从结果集中取出一个值时转换成 Java 类型

注册到typeHandlerRegistry中

private void typeHandlerElement(XNode parent) throws Exception {
   
   
    if (parent != null) {
   
   
        for (XNode child : parent.getChildren()) {
   
   
            //子节点为package时,获取其name属性的值,然后自动扫描package下的自定义typeHandler
            if ("package".equals(child.getName())) {
   
   
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
   
   
                //子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
                //javaType 是指定java类型
                //jdbcType 是指定jdbc类型(数据库类型: 如varchar)
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                //handler就是我们配置的typeHandler
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                //JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                //注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
                if (javaTypeClass != null) {
   
   
                    if (jdbcType == null) {
   
   
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
   
   
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
   
   
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}
pluginElement

解析plugins标签

  • StatementHandler
  • ParameterHandler
  • ResultSetHandler
  • Executor
private void pluginElement(XNode parent) throws Exception {
   
   
    if (parent != null) {
   
   
        for (XNode child : parent.getChildren()) {
   
   
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            // 我们在定义一个interceptor的时候,需要去实现Interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            interceptorInstance.setProperties(properties);
            // 向configuration对象中注册拦截器
            configuration.addInterceptor(interceptorInstance);
        }
    }
}
PropertyParser

PropertyParser这个类中包含一个内部私有的静态类VariableTokenHandler。VariableTokenHandler实现了TokenHandler接口,包含了一个Properties类型的属性,在初始化这个类时需指定该属性的值。VariableTokenHandler类对handleToken函数的具体实现如下:

https://blog.youkuaiyun.com/u014213453/article/details/40399475

③mapper元素解析

获取sql、加载接口

配置要注册的mapper有四种方式(优先级由高到低)

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
</mappers>

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
addMapper
mapperElement
// XMLConfigBuilder.java
// 解析configuration标签中的mapper子标签
private void mapperElement(XNode parent) throws Exception {
   
   
    if (parent != null) {
   
   
        for (XNode child : parent.getChildren()) {
   
   
            //mappers/ mappers/<package name="">
            // ① 找包下的接口,然后找同名的xml //这时候没有MSC
            if ("package".equals(child.getName())) {
   
   
                // 拿到包名
                String mapperPackage = child.getStringAttribute("name");
                // 得到包下所有的类,依次调用addMapper(mapperClass);,而addMapper(mapperClass)第一句就是判断这个类是不是接口,不是直接的话直接跳过
                configuration.addMappers(mapperPackage);
            } else {
   
   // mappers/<mapper resource="">
                // 获取resource/url/class属性
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");

                // 上面3个对应下面3个
                // ② resource属性不为空,如XxxMapper.xml
                if (resource != null && url == null && mapperClass == null) {
   
    // resource不为空
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                    // ③url,和上面的逻辑一样,就是解析xml而已
                } 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();
                    //  ④mapperClass
                } else if (resource == null && url == null && mapperClass != null) {
   
   
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    // 这个方法和上面resource里面最终依次调用的方法一样,就是对接口进行操作
                    configuration.addMapper(mapperInterface);
                } else {
   
   
                    throw new BuilderException("不是  url, resource or class报错");
                }
            }
        }
    }
}

// 上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件

在上面我们知道:

  • package和class就是扫描接口而已MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);,然后调用parser.parse();
    • config是MapperRegistry 类持有的私有属性
    • MapperRegistry类还有一个属性是knownMappers ,已经加载过的mapper集合,是从接口类到MapperProxyFactory的映射map,在addMapper(接口.class)的时候,第一句就是先knownMappers .put,告诉这个接口加载过了,即每个接口只能加载一次,否则报错
  • 而对于xml:

好吧我们先不管class的问题了,先看看如何把xml的内容解析好然后再调用addMapper了

上面解析mapper标签主要内容为把标签的内容放到XMLMapperBuilder实例中,然后调用parse方法,parse方法的作用是解析另一个xml文件(XxxMapper.xml)

addMappers

MapperXml解析好后,我们接着刚才的addMapper分析

[①]addMapper(包)
// 如果<mappers>标签里指定的是接口包路径的话
public void addMappers(String packageName) {
   
   
    mapperRegistry.addMappers(packageName);
}
//MapperRegistry
public void addMappers(String packageName) {
   
   
    addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
   
   
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 遍历包下的接口
    for (Class<?> mapperClass : mapperSet) {
   
   
        addMapper(mapperClass);
    }
}
[②]addMapper(接口)与MapperAnnotationBuilder
public <T> void addMapper(Class<T> type) {
   
   
    // 如果是接口
    if (type.isInterface()) {
   
   
        // 有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
        if (hasMapper(type)) {
   
   
            throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
        }
        boolean loadCompleted = false;
        try {
   
   
            // 没有被解析过该接口,开始解析该接口,放入已解析的map中,防止后面重复解析
            knownMappers.put(type, new MapperProxyFactory<T>(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.
            // 比如解析接口方法上的@Select(slect * from a)
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
   
   
            if (!loadCompleted) {
   
   
                // 如果未完成解析,移除key
                knownMappers.remove(type);
            }
        }
    }
}

虽然还没有学习解析xml,但还是想在这里加几句方便复习。在xml阶段底层还是会走到addMapper(接口)这里。hasMapper(type)这个判断也能倒推出xml的解析并没有解析完,只是把解析的信息放到configuration中,然后调用addMapper(接口),直到解析接口的时候拿到xml的信息,再加载注解的信息。所以注解的信息优先级高。

MapperAnnotationBuilder就是上面这个过程

[③]addMapper(xml)与XMLMapperBuilder

实际上是没有addMapper(xml)这个东西的,只有解析UserMapper.xml的过程

那我们看看如果是xml的话怎么办吧

倒退到解析xml文件

思想:解析xml的<mapper>标签元素,放到对应的接口

private void mapperElement(XNode parent) throws Exception {
   
   

    for (XNode child : parent.getChildren()) {
   
   
        if ("package".equals(child.getName())) {
   
   
            String mapperPackage = child.getStringAttribute("name");
            // 得到包下所有的类,依次调用addMapper(mapperClass);
            configuration.addMappers(mapperPackage);
        } else {
   
   // mappers/<mapper resource="">
            if (resource != null && url == null && mapperClass == null) {
   
    // resource不为空
                InputStream inputStream = Resources.getResourceAsStream(resource);
                // 解析mapper的xml // 传入了configuration参数,那么肯定就设置到configuration里面了
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration,
                                                                     resource, configuration.getSqlFragments());
                mapperParser.parse();
                // ③url,和上面的逻辑一样,就是解析xml而已
//XMLConfigBuilder.java
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

//XMLMapperBuilder.java
public void parse() {
   
   
    // resourse:"mapper/UserMapper.xml"
    if (!configuration.isResourceLoaded(resource)) {
   
   //从这里看出解析mapper.xml时会看有没有被解析过,如果解析过了config会知道,config持有一个Set<String>
        // 1
        /** ★★★解析mapper标签,即解析sql语句,将sql语句的所有信息封装成MappedStatement对象,然后存储在configuration对象中。
    */ 
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);// 解析后加到config的Set<String>中
        // 2
        /* 绑定mapper到接口类,里面有addMapper */
        bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

③.1

解析sql加到configuration中,解析XxxMapper.xml中,<mapper>标签下的子标签,如<select>,当然还包括<resultMap>

// XMLMapperBuilder.java
// 解析mapperXML里的configuration
private void configurationElement(XNode context) {
   
   //mapper标签里的内容
    try {
   
   
        String namespace = context.getStringAttribute("namespace");// 属性
        // 设置当前解析的接口。从这里也可以推测builderAssistant对象是全局单例的
        builderAssistant.setCurrentNamespace(namespace);
        cacheRefElement(context.evalNode("cache-ref"));//Ele才是子标签
        cacheElement(context.evalNode("cache"));
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        sqlElement(context.evalNodes("/mapper/sql"));
        // ★★★解析CRUD标签 // 参数类型为List<XNode> list
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//select子标签
    } catch (Exception e) {
   
   
        throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

③.1.1 解析mapper.xml中的每个sql

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
   
   
    // 我们只有一个select标签,所以list.size==1,如果还有别的标签就++
    for (XNode context : list) {
   
   
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
   
   
            // 去parse标签内容
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
   
   
            configuration.addIncompleteStatement(statementParser);
        }
    }
}
解析标签】③.1.1.1 parseStatementNode

解析mapperxml中的sql标签

public void parseStatementNode() {
   
   
    // 此时的id还只是方法名,如"selectUser"
    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,
    // in case if IncompleteElementException (issue #291)
    List<XNode> selectKeyNodes = context.evalNodes("selectKey");
    if (configuration.getDatabaseId() != null) {
   
   
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
    }
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);

    // 动态sql和静态sql  # 动态sql解析完问号都没有转
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    
    
    // 省略一些解析mapper文件里属性的内容
    。。。;
    // ★★★重点代码 // id:"selectUser" resource:"mapper/UserMapper.xml"
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                                        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
                                        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

③.1.1.1.1

addMappedStatement
public MappedStatement addMappedStatement(
    String id,//"selectUser",一会就拼接上类名了
    SqlSource sqlSource,
    StatementType statementType,// STATEMENT, PREPARED, CALLABLE
    SqlCommandType sqlCommandType,//UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
    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");
    }

    // 把selectUser加上全类名 "mapper.UserMapper.selectUser"
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)// mapper/UserMapper.xml
        .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) {
   
   //为null
        statementBuilder.parameterMap(statementParameterMap);
    }

    // MappedStatement解析,即sql的封装,只是封装了MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 放到configuration.mappedStatements这个map中,key为全类名+方法mapper.UserMapper.select,value为MappedStatement对象 // 一下放了两个key,全类名+方法、方法两个,总之出来的时候map.size已经是2了。可以通过ms.getId()得到key
    configuration.addMappedStatement(statement);
    return statement;
}

到这里解析完了xml到configuration.mappedStatements这个map中

③.2 bindMapperForNamespace():

接着往下执行XMLMapperBuilder.parse()

实际上就是解析该sql对应的class,并把该class放到configuration.mapperRegistry中。实际上mybatis的所有配置信息以及运行时的配置参数全部都保存在configuration对象中。mapperRegistry这个东西我们之前提过,是一个map,管理着接口到MapperProxyFactory的映射

// XMLMapperBuilder.java
private void bindMapperForNamespace() {
   
   
    // 我们是在解析每个mapper,设置过当前解析的接口是哪个,所以能拿到接口的namespace
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
   
   
        Class<?> boundType = null;
        try {
   
   
            // 获取接口类型
            boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
   
   
            //ignore, bound type is not required
        }
        // 如果接口类型不为空
        if (boundType != null) {
   
   
            // 如果mapperRegistry中没有该接口类型,mapperRegistry和configuration都是全局唯一的,知道接口有没有被解析过
            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
                // 前面我们也执行过一个同样的方法,只不过现在这个加了个namespace前缀放到这个set,现在这个set中有两个,"mapper/UserMapper.xml"和" configuration.addLoadedResource("namespace:" + namespace);
                configuration.addLoadedResource("namespace:" + namespace);
                // 又到了addMapper(接口)的时候了,config.addMapper内部就是调用的是mapperRegistry.addMapper(type);// 不管是config还是mapperRegistry都是唯一的,addMapper互相"重载"
                configuration.addMapper(boundType);
            }
        }
    }
}

还有一个小疑问:xml解析后怎么设置到configuration中的,让configuration知道xml的解析信息

分析了一下,原来是并没有把解析出来的xml信息和mapper接口关联,而是xml信息仅仅和configuration关联

然后直接去调用config.addMapper(接口)

[④]殊途同归

通过上面分析,不管是接口还是包还是xml,最终都是调用addMapper(接口),只不过包的话是挨个调用,xml的话是先封装xml的信息放到configuration中,addMapper时候再拿出来

回忆addMapper(接口类)

// MapperRegistry.java
public <T> void addMapper(Class<T> type) {
   
   
    // 如果是接口
    if (type.isInterface()) {
   
   
        // knownMappers<Class,MapperProxyFactory>中有没有该类型的mapper(用接口全类名作为key查),如果有则报错,每个接口只能解析一遍
        if (hasMapper(type)) {
   
   
            throw new BindingException("MapperRegistry中已经有这个类型了,不要重复解析。解析完的接口都会让MapperRegistry知道");
        }
        boolean loadCompleted = false;
        try {
   
   
            // 没有被解析过该接口,开始解析该接口,put(接口,创建代理工厂),防止后面重复解析
            knownMappers.put(type, new MapperProxyFactory<T>(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.
            
            // 比如解析接口方法上的@Select(slect * from a)
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
   
   
            if (!loadCompleted) {
   
   
                knownMappers.remove(type);
            }
        }
    }
}

好吧,就是new了个MapperAnnotationBuilder,然后parse

MapperAnnotationBuilder

下面的parse是addMapper的parse,即对接口的parse,把xml的信息和注解信息都放到接口中

接下来依次解析xml和注解形式的sql。注解会覆盖xml的

// MapperAnnotationBuilder.java
public void parse() {
   
   
    // type是接口的名字 ,接口的toString方法是形如"interface mapper.UserMapper"
    String resource = type.toString();
    // 判断接口是否被加载过
    if (!configuration.isResourceLoaded(resource)) {
   
   
        // 如果这个接口关联着之前的xml解析出来的内容,那么添加一下 // 如果我们在<mappers>标签里指定的是class,那么进入到这里去加载对于的xml,而resource的方式我们已经加载过了,所以进去又跳出来了
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        // 获取接口中的方法
        Method[] methods = type.getMethods();
        // 遍历接口当前的方法看有没有注解 // 理论上接口上的注解会覆盖xml里的,但是实际上是报错
        for (Method method : methods) {
   
   
            try {
   
   
                // 解析注解,没有注解又跳出来了
                parseStatement(method);//解析xml的时候把sql语句翻译成MappedStatement对象,加到configuration全局对象中//也就是放到configuration.mappedStatement这个StrictMap中(继承hashmap),这个map的特点是第二个put时直接抛异常,也就是避免了xml和注解方式的重复
            } catch (IncompleteElementException e) {
   
   
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
        }
    }
    parsePendingMethods();
}


// 
private void loadXmlResource() {
   
   
    // config和MapperRegistry是全局唯一的,他们知道xml是否被解析过
    // 加载一下,而对于resource方式的,在bindmapper阶段,我们已经加过了,所以不进if
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
   
   
        // 拼凑接口对应的xml地址
        String xmlResource = type.getName().replace('.', '/') + ".xml";
        InputStream inputStream = null;
        try {
   
   
            // 看有没有xml文件
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e) {
   
   
            // ignore, resource is not required
        }
        // 是否存在xml一个文件
        if (inputStream != null) {
   
   
            // 又是一个XML Builder,解析配置文件
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
                                                              xmlResource, // 
                                                              configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
        }
    }
}
@Select

在刚才的解析中,我们说了如果是xml没有相应注解的话直接跳出来了,如果有注解的话会进入parseStatement,他的最

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值