MyBatis源码分析篇(一)----初始化之XMLConfigBuilder#parse

源起

​ 在构建会话工厂类的时候,会解析全局配置文件,然后将相关信息存储值Configuration中;解析配置文件入口:org.apache.ibatis.builder.xml.XMLConfigBuilder#parse,所以我们这一篇博文呢,就以这个方法为切入口,分析一下MyBatis初始化的相关操作源码。

源码分析

1. XMLConfigBuilder#parseConfiguration
  • 首先parse()方法会调用parseConfiguration(XNode root),这里的XNode是指根节点configuration下所有的节点内容(parser.evalNode("/configuration")),这里解析XML采用的是XPath方法。

  • 下面的代码是我们项目中经常用到的mybatis-conf.xml全局配置文件相关代码,接下来我们分析各节点的内容解析:

  • <configuration>
        <!-- 引入jdbc配置文件 -->
        <properties resource="db.properties"/>
        
        <!-- settings配置信息 -->
        <settings>
            <!-- 该配置影响的所有映射器中配置的缓存的全局开关。 -->
            <setting name="cacheEnabled" value="true"/>
            <!-- 延迟加载的全局开关 -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!-- 是否允许单一语句返回多结果集(需要兼容驱动) -->
            <setting name="multipleResultSetsEnabled" value="true"/>
            <!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找,可选值:SLF4J|LOG4J|LOG4J2|JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING-->
            <setting name="logImpl" value="STDOUT_LOGGING"/>
            <!-- 还有很多,在此就不一一列举了 -->
        </settings>
    
        <!-- 定义别名 -->
        <typeAliases>
            <!--   <typeAlias type="org.mybatis.example.pojo.Blog" alias="blog" /> --><!-- 手动定义别名 -->
            <!--   扫描包,自动以类名作别名 -->
            <package name="org.mybatis.example.pojo"/>
        </typeAliases>
        
        <!-- 插件扩展 -->
        <plugins>
            <plugin interceptor="org.mybatis.example.ExamplePlugin">
                <property name="someProperty" value="100"/>
            </plugin>
        </plugins>
     
    	<!-- 定义数据源 -->
        <environments default="development">
            <environment id="development">
                <!-- 配置事务管理 -->
                <transactionManager type="JDBC"/>
                <!-- 配置数据源 -->
                <dataSource type="POOLED">
                    <!--下面的属性值必须和db.properties中的key对应 -->
                    <property name="driver" value="${jdbc.driver}"/>
                    <property name="url" value="${jdbc.url}"/>
                    <property name="username" value="${jdbc.username}"/>
                    <property name="password" value="${jdbc.password}"/>
                </dataSource>
            </environment>
        </environments>
      
    	<!-- 定义映射文件 -->
        <mappers>
            <!--  手动绑定映射文件   <mapper resource="mapper/BlogMapper.xml"/> -->
            <!-- 扫描包,自动绑定映射文件 -->
            <package name="org.mybatis.example.dao"/>
        </mappers>
    </configuration>
    
2. XMLConfigBuilder#propertiesElement
  • org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement,该方法是解析properties节点内容;

  • <properties resource="org/mybatis/example/db.properties">
          <property name="username" value="dev_user"/>
          <property name="password" value="F2Fa3!33TYyg"/>
    </properties>
    <!-- 或 -->
    <!-- 引入jdbc配置文件 -->
    <properties resource="db.properties"/>
    
3. XMLConfigBuilder#settingsAsProperties
  • org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsAsProperties,该方法主要用来解析<settings>节点的内容;

  • 这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为

  • <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
    
  • 可查看官网介绍说明:settings

  • 结合org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement方法

4. XMLConfigBuilder#typeAliasesElement
  • org.apache.ibatis.builder.xml.XMLConfigBuilder#typeAliasesElement类型别名,<typeAliases>节点解析;

  • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写;

  • <typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
        <typeAlias alias="Comment" type="domain.blog.Comment"/>
        <typeAlias alias="Post" type="domain.blog.Post"/>
        <typeAlias alias="Section" type="domain.blog.Section"/>
        <typeAlias alias="Tag" type="domain.blog.Tag"/>
    </typeAliases>
    <!-- 或 -->
    <typeAliases>
        <package name="org.mybatis.example.pojo"/>
    </typeAliases>
    
5. XMLConfigBuilder#pluginElement
  • MyBatis允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

    • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    • ParameterHandler (getParameterObject, setParameters)
    • ResultSetHandler (handleResultSets, handleOutputParameters)
    • StatementHandler (prepare, parameterize, batch, update, query)
  • 这也就是我们可以进行插件扩展的地方,比如大名鼎鼎的分页插件:PageHelper就是利用插件原理实现分页的。

  • 配置设置:

  • <plugins>
        <plugin interceptor="org.mybatis.example.ExamplePlugin">
            <property name="someProperty" value="100"/>
        </plugin>
    </plugins>
    
  • @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
    public class ExamplePlugin implements Interceptor {
        private Properties properties = new Properties();
        public Object intercept(Invocation invocation) throws Throwable {
            // implement pre processing if need
            Object returnObject = invocation.proceed();
            // implement post processing if need
            return returnObject;
        }
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    }
    
  • 上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor是负责执行底层映射语句的内部对象。

6. XMLConfigBuilder#objectFactoryElement
  • org.apache.ibatis.builder.xml.XMLConfigBuilder#objectFactoryElement
  • 每次MyBatis创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作
7. XMLConfigBuilder#environmentsElement
  • org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement,环境配置,解析<environments>节点

  • <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="..." value="..."/>
            </transactionManager>
            <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>
    
  • 注意一些关键点:

    • 默认使用的环境 ID(比如:default=“development”)。
    • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
    • 事务管理器的配置(比如:type="JDBC")。
    • 数据源的配置(比如:type=“POOLED”)。
  • 默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

7.1. XMLConfigBuilder#transactionManagerElement
  • 解析<transactionManager>节点,设置事务管理器信息;

  • MyBatis中有两种类型的事务管理器(也就是type="[JDBC|MANAGED]"):

    • JDBC– 这个配置直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

    • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为 false 来阻止默认的关闭行为。

    • <transactionManager type="MANAGED">
          <property name="closeConnection" value="false"/>
      </transactionManager>
      
  • 如果使用Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置

7.2. XMLConfigBuilder#dataSourceElement
  • 数据源解析

  • dataSource元素使用标准的JDBC 数据源接口来配置JDBC连接对象的资源。

  • 大多数MyBatis应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
  • 有三种内建的数据源类型(也就是type="[UNPOOLED|POOLED|JNDI]");
    • UNPOOLED: 这个数据源的实现会每次请求时打开和关闭连接;
    • POOLED:这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间;
    • JNDI: 这个数据源实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。
8. XMLConfigBuilder#typeHandlerElement
  • 类型处理器

  • MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成Java类型

  • <typeHandlers>
        <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
    </typeHandlers>
    <!-- 或 -->
    <typeHandlers>
        <package name="org.mybatis.example"/>
    </typeHandlers>
    
9. XMLConfigBuilder#mapperElement
  • 至此,MyBatis的行为已经由上述元素配置完成,现在我们就要定义SQL映射语句了。 所以,首先我们需要告诉MyBatis到哪里去找到这些语句。

  • <!-- 使用相对于类路径的资源引用 -->
    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
        <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    <!-- 或 -->
    <!-- 使用完全限定资源定位符(URL) -->
    <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
        <mapper url="file:///var/mappers/BlogMapper.xml"/>
        <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    <!-- 或 -->
    <!-- 使用映射器接口的完全限定类名 -->
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
        <mapper class="org.mybatis.builder.BlogMapper"/>
        <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    <!-- 或 -->
    <!-- 将包内的映射器接口全部注册为映射器 -->
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
    
  • 下面我们来分析一下源码。

9.1. Configuration#addMappers
  • <!-- 将包内的映射器接口实现全部注册为映射器 -->
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
    <!-- 或 -->
    <!-- 使用映射器接口的完全限定类名 -->
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
        <mapper class="org.mybatis.builder.BlogMapper"/>
        <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
  • 下面就这两种方式的加载,分析一下源码

  • 上面两个方法最终都会间接或直接的调用到org.apache.ibatis.binding.MapperRegistry#addMapper

  • Mapper接口存储在名为knownMappersMap中,key值为接口类型,value是其接口类型的映射器代理工厂类;该代理类会在调用mapper接口中方法时,获取接口的代理类MapperProxy

  • 下来会调用org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource方法

  • 上图代码中**String xmlResource = type.getName().replace('.', '/') + ".xml";**,是将包名中的“.”替换为“/”,既:在同包路径下找xml文件;如果运行报诸如:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found).....的错误:需要注意查看同包下是否有对应xml,如果有,则需要看编译时是否将xml编辑进去了,如果target中没有编译后的xml,则需要在pom文件(Maven管理的项目)中配置:

  • <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <!--默认是true-->
                <!--<filtering>true</filtering>-->
            </resource>
        </resources>
    </build>
    
  • 获取到XML文件流之后,会调用org.apache.ibatis.builder.xml.XMLMapperBuilder#parse进行解析。

  • 针对上面的四种配置方式,其实最终都会走到下面这个方法

9.2. XMLMapperBuilder#configurationElement
  • org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement方法执行xml配置文件的解析,构建SQL语句;

  • 最终代码会走向org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode,进行构建MappedStatement,并将其添加到Configuration#mappedStatements中去。

结语

​ 至此,我们构建好了:执行sqlExecutor、保存了SQL信息的MappedStatement等等信息,然后当我们调用mapper.queryById(String)的时候,首先会在Configuration中的mappedStatements中获取一个对应类型的MappedStatement,然后会使用executor.query去执行查询操作。

发布了5 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41244651/article/details/105646654