源起
在构建会话工厂类的时候,会解析全局配置文件,然后将相关信息存储值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
去执行查询操作。