Mybatis初始化加载流程————配置文件解析

本次测试的实例中的相关配置文件如下:
application.xml文件:

version="1.0" encoding="UTF-8"?>
"http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="name" value="mysql"/>
        <property name="url" value="xxxxxxx"/>
        <property name="username" value="xxxxxxx"/>
        <property name="password" value="zh4y4q5ang"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    
    id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mybatis/*.xml"/>
        <property name="typeAliasesPackage" value="com.entities"/>
        <property name="configLocation" value="classpath:config/configuration.xml"/>
    
    id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.interfaces"/>
    
    id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    

configuration.xml文件:



<configuration>
    <plugins>
        <plugin interceptor="com.interceptors.LogInterceptor" >plugin>
    plugins>
configuration>

CityMapper.xml文件:



<mapper namespace="com.interfaces.CityMapper">
<resultMap id="BaseResultMap" type="com.entities.City">
    <id column="ID" jdbcType="INTEGER" property="id"/>
    <result column="Name" jdbcType="CHAR" property="name"/>
    <result column="CountryCode" jdbcType="CHAR" property="countrycode"/>
    <result column="District" jdbcType="CHAR" property="district"/>
    <result column="Population" jdbcType="INTEGER" property="population"/>
resultMap>
<select id="selectCityById" parameterType="int" resultType="com.entities.City">
    SELECT * FROM city WHERE ID=#{id,jdbcType=INTEGER}
select>
<insert id="insertCity" parameterType="com.entities.City">
    insert into city
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="id != null">
            ID,
        if>
        <if test="name != null">
            Name,
        if>
        <if test="countrycode != null">
            CountryCode,
        if>
        <if test="district != null">
            District,
        if>
        <if test="population != null">
            Population,
        if>
    trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
        <if test="id != null">
            #{id,jdbcType=INTEGER},
        if>
        <if test="name != null">
            #{name,jdbcType=CHAR},
        if>
        <if test="countrycode != null">
            #{countrycode,jdbcType=CHAR},
        if>
        <if test="district != null">
            #{district,jdbcType=CHAR},
        if>
        <if test="population != null">
            #{population,jdbcType=INTEGER},
        if>
    trim>
insert>
<delete id="deleteCityById" parameterType="java.lang.Integer">
    delete from city
    where ID = #{id,jdbcType=INTEGER}
delete>

<update id="updateCityById" parameterType="com.entities.City">
    UPDATE city set Name=#{name,jdbcType=CHAR},
    CountryCode=#{countrycode,jdbcType=CHAR},
    District=#{district,jdbcType=CHAR},
    Population=#{population,jdbcType=INTEGER}
    WHERE ID=#{id,jdbcType=INTEGER}
update>
    <select id="selectWithIf" parameterType="map" resultMap="BaseResultMap">
        SELECT * FROM city <if test="id!=0">WHERE city.id=#{id}if> <if test="id==0" >limit 2if>
    select>

    <select id="selectWithChoose" parameterType="map" resultMap="BaseResultMap">
        SELECT * FROM city
        <choose >
            <when test="id==1">
                limit 1
            when>
            <when test="id==2">
                limit 2
            when>
            <otherwise>
                WHERE city.id=#{id}
            otherwise>
        choose>
    select>
    <select id="selectWithForeach" parameterType="map" resultMap="BaseResultMap">
        SELECT * FROM city
            WHERE city.id in
        <foreach collection="ids" item="tmp" open="(" close=")" separator="," index="">
            #{tmp.id}
        foreach>
    select>
mapper>

在初始化spring框架的时候,会将在application.xml声明的bean注入到spring的上下文中。包括:
初始化dataSource,并将DataSource注入到spring框架中。
初始化SqlSessionFactoryBean对象,并将DataSource的实体对象注入到SqlSessionFactory中。
SqlSessionFactoryBean实现InitializingBean方法,在其实现的方法中,创建一个SqlSessionFactory对象,具体实现为DefaultSqlSessionFactory类,内部传入configuration对象。关于配置文件的主要解析过程就是在这里实现的。
下面来分析一下源码:

    @Override
    public void afterPropertiesSet() throws Exception {
        notNull(dataSource, "Property 'dataSource' is required");
        notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
                "Property 'configuration' and 'configLocation' can not specified with together");
        //创建一个SqlSessionFactory对象,具体实现为DefaultSqlSessionFactor类
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

        Configuration configuration;
        //刚开始时,this.configuration为空,在测试中设置了mybatis的configuration的xml配置文件
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
            configuration = this.configuration;
            if (configuration.getVariables() == null) {
                configuration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                configuration.getVariables().putAll(this.configurationProperties);
            }
        } else if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            //从XmlConfigBuilder中构建一个Configuration
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            //直接新建一个Configuration对象
            configuration = new Configuration();
            if (this.configurationProperties != null) {
                configuration.setVariables(this.configurationProperties);
            }
        }

        if (this.objectFactory != null) {
            configuration.setObjectFactory(this.objectFactory);
        }

        if (this.objectWrapperFactory != null) {
            configuration.setObjectWrapperFactory(this.objectWrapperFactory);
        }

        if (this.vfs != null) {
            configuration.setVfsImpl(this.vfs);
        }

        //测试中设置了typeAliasesPackage
        if (hasLength(this.typeAliasesPackage)) {
            //根据,或者;对一条字符串进行分割,得到用来存放实体类的包名
            String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeAliasPackageArray) {
                //将对数据库记录封装的包名保存在Configuration对象中
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
            }
        }

        if (!isEmpty(this.typeAliases)) {
            for (Class typeAlias : this.typeAliases) {
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
            }
        }

        if (!isEmpty(this.plugins)) {
            for (Interceptor plugin : this.plugins) {
                //设置类一个log拦截器
                configuration.addInterceptor(plugin);
                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
            }
        }

        if (hasLength(this.typeHandlersPackage)) {
            String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeHandlersPackageArray) {
                configuration.getTypeHandlerRegistry().register(packageToScan);
                LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
            }
        }

        if (!isEmpty(this.typeHandlers)) {
            for (TypeHandler typeHandler : this.typeHandlers) {
                configuration.getTypeHandlerRegistry().register(typeHandler);
                LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
            }
        }

        //没有设置databaseId
        if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
            try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
            }
        }

        //测试中,没有使用缓存
        if (this.cache != null) {
            configuration.addCache(this.cache);
        }

        if (xmlConfigBuilder != null) {
            try {
                //开始解析configurationxml配置文件
                //由于在测试的configuration配置文件中,只是定义了一个plugin,在解析的时候,直接解析出plugin的实现类,并将plugin的实体对象保存在configuration的实体对象中。
                xmlConfigBuilder.parse();
                LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
            } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }

        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

        //从这里开始,处理mapper.xml文件
        if (!isEmpty(this.mapperLocations)) {
            for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                    continue;
                }

                try {
                    //创建一个XMLMapperBuilder用来解析定义sql语句的xml文件
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    //开始解析mapper.xml文件
                    xmlMapperBuilder.parse();
                } catch (Exception e) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                } finally {
                    ErrorContext.instance().reset();
                }
                LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
            }
        } else {
            LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
        }
        //返回一个SqlSessionFactory实体对象
        return this.sqlSessionFactoryBuilder.build(configuration);
    }

下面看看如何解析mapper.xml文件的:

    public void parse() {
        //判断当前mapper.xml文件是否已经被解析过
        if (!configuration.isResourceLoaded(resource)) {
            //在configurationElement完成mapper.xml中所有元素的解析,并将解析出来的相关属性全部保存在configuration中
            configurationElement(parser.evalNode("/mapper"));
            //添加已经加载过的mapper文件
            configuration.addLoadedResource(resource);
            //绑定sql语句与Java中定义的接口
            bindMapperForNamespace();
        }
        //这个几个方法都是将前面判断是不完整的语句再次解析一下,就不多说了
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }

    private void configurationElement(XNode context) {
        try {
            //namespace就是定义的接口:com.interfaces.CityMapper
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            //想builderAssistant设置解析的文件对应的java接口
            builderAssistant.setCurrentNamespace(namespace);
            //解析出标签,测试中没有使用此标签
            cacheRefElement(context.evalNode("cache-ref"));
            //解析出标签,测试中没有使用此标签
            cacheElement(context.evalNode("cache"));
            //解析标签,这个标签官方已经不推荐使用了
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //解析标签
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            //测试中没有使用此标签
            sqlElement(context.evalNodes("/mapper/sql"));
            //解析