源起
在构建会话工厂类的时候,会解析全局配置文件,然后将相关信息存储至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接口存储在名为knownMappers的Map中,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中去。
结语
至此,我们构建好了,执行sql的Executor、保存了SQL信息的MappedStatement等等信息,然后当我们调用mapper.queryById(String)的时候,首先会在Configuration中的mappedStatements中获取一个对应类型的MappedStatement,然后会使用executor.query去执行查询操作。