前言
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解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。