在构建会话工厂类的时候,会解析全局配置文件,然后将相关信息存储值Configuration
中;解析配置文件入口:org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
,所以我们这一篇博文呢,就以这个方法为切入口,分析一下MyBatis
初始化的相关操作源码。
XMLConfigBuilder#parseConfiguration
首先parse()
方法会调用parseConfiguration(XNode root)
,这里的XNode
是指根节点configuration
下所有的节点内容(parser.evalNode("/configuration")
),这里解析XML
采用的是XPath
方法。
下面的代码是我们项目中经常用到的mybatis-conf.xml
全局配置文件相关代码,接下来我们分析各节点的内容解析:
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
<typeAliases>
<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">
<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>
<package name="org.mybatis.example.dao"/>
mappers>
configuration>
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>
<properties resource="db.properties"/>
XMLConfigBuilder#settingsAsProperties
org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsAsProperties
,该方法主要用来解析
节点的内容;
这是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
方法
XMLConfigBuilder#typeAliasesElement
org.apache.ibatis.builder.xml.XMLConfigBuilder#typeAliasesElement
类型别名,
节点解析;
类型别名可为 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>
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
是负责执行底层映射语句的内部对象。
XMLConfigBuilder#objectFactoryElement
org.apache.ibatis.builder.xml.XMLConfigBuilder#objectFactoryElement
MyBatis
创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory
)实例来完成实例化工作XMLConfigBuilder#environmentsElement
org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement
,环境配置,解析
节点
<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>
注意一些关键点:
type="JDBC"
)。默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
XMLConfigBuilder#transactionManagerElement
解析
节点,设置事务管理器信息;
在MyBatis
中有两种类型的事务管理器(也就是type="[JDBC|MANAGED]"
):
JDBC
– 这个配置直接使用了JDBC
的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED
– 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE
应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection
属性设置为 false 来阻止默认的关闭行为。
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
transactionManager>
如果使用Spring + MyBatis
,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置
XMLConfigBuilder#dataSourceElement
数据源解析
dataSource
元素使用标准的JDBC
数据源接口来配置JDBC
连接对象的资源。
MyBatis
应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。type="[UNPOOLED|POOLED|JNDI]"
);
UNPOOLED
: 这个数据源的实现会每次请求时打开和关闭连接;POOLED
:这种数据源的实现利用“池”的概念将JDBC
连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间;JNDI
: 这个数据源实现是为了能在如EJB
或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI
上下文的数据源引用。XMLConfigBuilder#typeHandlerElement
类型处理器
MyBatis
在设置预处理语句(PreparedStatement
)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成Java
类型
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
typeHandlers>
<typeHandlers>
<package name="org.mybatis.example"/>
typeHandlers>
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>
<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>
下面我们来分析一下源码。
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/javadirectory>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
build>
获取到XML
文件流之后,会调用org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
进行解析。
针对上面的四种配置方式,其实最终都会走到下面这个方法
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
去执行查询操作。
微信公众号:从Demo到折腾源码
微信号:albert_ztym