
前言

XMLConfigBuilder是BaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
使用
调用XMLConfigBuilder进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。
实例化XMLConfigBuilder对象。
1 | private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { |
实例化Configuration
通过new Configuration()的方式实例化:
typeAliasRegistry是一个类型别名注册器,实现原理就是维护一份HashMap,别名作为key,类的全限定名作为value。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias代码如下:
1 | public void registerAlias(String alias, Class<?> value) { |
在实例化Configuration类过程中,在该类里除了实例化了TypeAliasRegistry还实例化了另外一个下面用到的的类:
1 | // 类型处理器注册器 |
TypeHandlerRegistry和TypeAliasRegistry实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry的属性
1 | // jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型 |
TypeHandlerRegistry构造函数:
1 | public TypeHandlerRegistry() { |
里面调用了两个register()重载方法, type + handler 参的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)和 type + jdbc type + handler 参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)
1 | // java type + handler |
type + handler方法:先获取处理器的MappedJdbcTypes注解(自定义处理器注解),若注解的value值不为空时,由于该值为JdbcType[]类型,所以for循环javaType+jdbcType+TypeHandler注册,若includeNullJdbcType(jdbcType是否包含null)为true,默认值为false,注册到相应映射中。若注解的value为null,直接调用注册操作,里面不会注册type + jdbc type + handler关系。type + jdbc type + handler方法:该方法将java类强制转换为java.lang.reflect.Type类型,然后调用最终注册的方法。
调用父类BaseBuilder的构造方法
BaseBuilder定义有三个属性
1 | protected final Configuration configuration; |
BaseBuilder构造方法
1 | public BaseBuilder(Configuration configuration) { |
这里属性,就是上面讲解到的。
调用 XMLConfigBuilder.parse() 作为解析入口。
parse()实现配置文件是否解析过
1 | public Configuration parse() { |
解析/configuration里的配置
1 | private void parseConfiguration(XNode root) { |
从上面源码中,不难看出这里是解析/configuration中的各个子节点。
properties 节点解析
properties配置方式
1 | <!-- 方法一 --> |
propertiesElement()方法
1 | private void propertiesElement(XNode context) throws Exception { |
从上面源码中,resource和url的配置形式不允许同时存在,否则抛出BuilderException异常。先解析propertie的配置值,再解析resource或url的值。
当propertie存在与resource或url相同的key时,propertie的配置会被覆盖,应为Properties实现的原理就是继承的Hashtable类来实现的。
settings 节点解析
settings配置方式
1 | <settings> |
设置中各项的意图、默认值 图(引用来源:w3cschool)
| 设置参数 | 描述 | 有效值 | 默认值 |
|---|---|---|---|
| cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true |
| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true,false | false |
| aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false,true | |
| multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true,false | true |
| useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true,false | true |
| useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true,false | False |
| autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
| defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
| defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
| safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true,false | False |
| mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | False |
| localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION,STATEMENT | SESSION |
| jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
| lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString |
| defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
| callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true,false | false |
| logPrefix | 指定 MyBatis 增加到日志名称的前缀。 Any String | Not set | |
| logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING | Not set |
| proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB |
settingsAsProperties()方法
1 | private Properties settingsAsProperties(XNode context) { |
这里获取到setting的值,并返回Properties对象。然后做配置的name是否合法。org.apache.ibatis.reflection.MetaClass类是保存着一个利用反射获取到的类信息,metaConfig.hasSetter(String.valueOf(key))是判断metaConfig对象中是否包含key属性。
vfsImpl()方法
1 | private void loadCustomVfs(Properties props) throws ClassNotFoundException { |
该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在Configuration.vfsImpl中。
settingsElement()方法
这个方法的作用就是将解析的settings设置到 configuration中
1 | private void settingsElement(Properties props) throws Exception { |
typeAliases 节点解析
typeAliases配置方式
1 | <typeAliases> |
该节点是配置类和别名的关系
package节点是配置整个包下的类typeAlias节点是指定配置单个类,type为必填值且为类全限定名,alias为选填。
配置后,是该类时,可直接使用别名。
typeAliasesElement()方法
1 | private void typeAliasesElement(XNode parent) { |
使用 package 配置
当扫描package时,获取到包名后TypeAliasRegistry.registerAliases(typeAliasPackage)
1 | public void registerAliases(String packageName){ |
扫描到指定package下所有以.class结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。
最后TypeAliasRegistry.registerAlias(Class<?> type)注册到别名注册器中。
1 | public void registerAlias(Class<?> type) { |
通过类注册到注册器中时,如果该注册类有使用@Alias(org.apache.ibatis.type.Alias)注解,那么XML配置中配置的别名会被注解配置覆盖。
使用 typeAlias 配置
如果typeAlias的alias有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。
plugins 节点解析
plugins配置方式
1 | <plugins> |
自定义插件需要实现org.apache.ibatis.plugin.Interceptor接口,同时在注解上指定拦截的方法。
pluginElement()方法
1 | private void pluginElement(XNode parent) throws Exception { |
这里取<plugin>节点的interceptor可以使用别名设置。从源码中resolveClass方法
1 | // |
以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过Resources.classForName生成实例。
objectFactory,objectWrapperFactory,reflectorFactory 节点解析
以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在configuration。
1 | // objectFactory 解析 |
以上为解析objectFactory,objectWrapperFactory,reflectorFactory源码,经过前面的分析后,这里比较容易看懂。
environments 节点解析
environments配置方式
1 | <environments default="development"> |
该节点可设置多个环境,针对不同的环境单独配置。environments的属性default是默认环境,该值对应一个environment的属性id的值。
transactionManager为事务管理,属性type为事务管理类型,上面的介绍的new Configuration()有定义类型有:JDBC 和 MANAGED事务管理类型。dataSource是数据源,type为数据源类型,与transactionManager同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。
environmentsElement()方法
1 | private void environmentsElement(XNode context) throws Exception { |
若没有配置environment环境或环境没有给id属性,则会抛出异常,若当前id是要使用的就返回true,否则返回false。TransactionFactory实例化过程比较简单,与创建DataSourceFactory类似。
数据源的获取
获取数据源,首先得创建DataSourceFactory,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))创建
1 | private DataSourceFactory dataSourceElement(XNode context) throws Exception { |
这里就是获取到数据源得type后,利用上面所讲到得resolveClass()方法获取到DataSourceFactory。
以UNPOOLED为例,对应的DataSourceFactory实现类为UnpooledDataSourceFactory。实例化过程中就给该类的属性dataSource数据源赋值了
1 | /** |
UnpooledDataSource类里面有静态代码块所以数据源被加载
1 | /** |
databaseIdProvider 节点解析
databaseIdProvider配置方式
1 | <databaseIdProvider type="DB_VENDOR"> |
基于映射语句中的databaseId属性,可以根据不同数据库厂商执行不同的sql。
databaseIdProviderElement()方法
1 | private void databaseIdProviderElement(XNode context) throws Exception { |
根据匹配的数据库厂商类型匹配数据源databaseIdProvider.getDatabaseId(environment.getDataSource())
1 |
|
这里需要注意的是配置:比如使用mysql,我踩过这里的坑,这里Name为MySQL,我把y写成大写,结果匹配不上。
另外这里写个My也能匹配上,应为是使用的String.contains方法,只要包含就会符合,这里代码应该不够严谨。
typeHandlers 节点解析
typeHandlers配置方式
1 | <typeHandlers> |
扫描整个包或者指定类型之间的映射,javaType, jdbcType非必需,handler必填项
typeHandlerElement()方法
1 | private void typeHandlerElement(XNode parent) throws Exception { |
源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的type + handler和type + jdbc type + handler不同情况注册。
另外这里还有个TypeHandlerRegistry.register(Class<?> typeHandlerClass)注册类
1 | public void register(Class<?> typeHandlerClass) { |
以上的register方法中,了解type + jdbc type + handler后,其他的register重载方法比较容易理解,其他的都是基于它上面的封装。
mappers 节点解析
mappers配置方式
1 | <mappers> |
可通过以上四种形式配置mappers节点,<package>和<mapper>为互斥节点。
mapperElement()方法
该方法是负责解析<mappers>节点
1 | private void mapperElement(XNode parent) throws Exception { |
<package>的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和<mapper>使用class属性是一样逻辑。
解析package方式
1 | // Configuration 中定义了 |
解析mapper的class属性
1 | // 该函数于 Configuration 中 |
这两中方式是直接注册接口到mapperRegistry,另外两种是解析xml的方式就是获取映射文件的namespace,再注册进来,XMLMapperBuilder是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。