mybatis 配置文件解析
从SqlSessionFactory创建说起
使用
SqlSessionFactoryBuilder
解析传入的mybatis.xml配置, 可以创建出SqlSessionFactory
.
如下为创建SqlSessionFactory
的方法.
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("config/mybatis-config.xml");
return sqlSessionFactoryBuilder.build(inputStream);
- xml文件的解析是通过
XMLConfigBuilder
的parse
方法进行的. 该方法解析之后会返回一个Configuration
对象. - 最后调用
build
方法, 将Configuration
对象作为参数, 实例化DefaultSqlSessionFactory
, 得到SqlSessionFactory
.
XMLConfigBuilder
初始化
XMLConfigBuilder
对象需要持有Xml解析器XPathParser
, 激活的当前环境信息, 属性参数. 其构造器见下:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
这里首先初始化了一个
Configuration
. 并将parsed
设置状态(未解析).
调用parse方法解析xml
parse
方法调用后首先根据parsed
判断是否已经进行过解析. 没有则将其设置为true.- 之后调用
parseConfiguration
方法, 该方法会解析xml中的各节点配置, 并给configuration
赋值.
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
evalNode
方法通过Xpath表达式获取xml中的节点信息(结果封装为: XNode, 它是xml节点的类型,属性,名称xpath表达式等的表示).parser.evalNode("/configuration")
就获取到了xml配置中的configuration
节点. 并将其作为获取后续节点进行解析的根节点.
配置文件解析
properties节点
该节点的配置方式如下, resource
指定了参数配置文件的位置
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
对该节点的解析使用propertiesElement
方法, 方法的输入参数为root.evalNode("properties")
, 即properties
节点的封装.
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
该方法主要用于获取mybatis的所有配置信息, 主要来自于4个方面:
- 配置文件中的
properties
下property
中的配置. properties
中的resource
属性所指位置的配置文件properties
中的url
属性所指位置的配置文件configuration
对象中的属性配置, 一般程序在创建XMLConfigBuilder
会进行设置.
以上的属性都会重新设置到configuration
对象中去, 并交给解析器进行处理.
getChildrenAsProperties
: 获取当前节点下的子节点, 将节点的name和value属性值, 存放到Properties
中.
settings节点
该节点的属性配置是我们操作的重点. 配置如下:
<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>
对应的解析方法为settingsAsProperties
, 该方法同样使用getChildrenAsProperties
获取子节点中的键值, 并返回Properties
对象.
之后对设置的参数进行处理, 相关方法见下:
loadCustomVfs(settings);
loadCustomLogImpl(settings);
....
settingsElement(settings);
loadCustomVfs
: 指定VFS(虚拟文件系统)的实现.
该方法将字符串配置反射为Class, 配置到Configuration
的Class<? extends VFS> vfsImpl
属性上去.loadCustomLogImpl
: 指定 MyBatis 所用日志的具体实现,未指定时将自动查找.
可使用的日志框架及实现如下:typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
以上配置发生在
Configuration
构造器中.
获取到对应的实现类后, 配置到Configuration
的Class<? extends Log> logImpl
属性上去.settingsElement
: 设置xml中的配置对应Configuration
中的属性.
typeAliases节点
typeAliasesElement
方法为处理typeAliases
节点的方法. 该方法对两种子节点进行处理:
package
typeAlias
获取到节点的属性配置后, 通过typeAliasRegistry.registerAlias
将其添加到private final Map<String, Class<?>> typeAliases = new HashMap<>();
中缓存.
plugins插件节点
使用该节点首先需要实现Interceptor
接口, 实现intercept
方法, 或者重写setProperties
, 用于给当前拦截器设置属性.
该节点配置方式如:
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
pluginElement
方法, 见下:
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
通过反射获取拦截器对象, 将其加入到拦截器调用链上去. 该调用链在InterceptorChain
类中定义为:private final List<Interceptor> interceptors = new ArrayList<>();
objectFactory节点
该节点为对象工厂配置, mybatis会使用对象工厂创建实例.
因此需要将实现了ObjectFactory
的类配置到该节点的type
属性上. mybatis提供了该接口的默认实现DefaultObjectFactory
. 可以继承该类并做一些自定义.
该节点的解析使用objectFactoryElement
方法, 通过反射获取实例, 并设置到Configuration
对象上.
objectWrapperFactory节点
objectWrapperFactoryElement
用于解析该节点.
节点配置类需要实现ObjectWrapperFactory
接口.
reflectorFactory节点
reflectorFactoryElement
用于解析该节点.
节点配置类需要实现ReflectorFactory
接口.
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>
environmentsElement
用于解析该节点的配置. 以下为解析代码:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
- 如果创建
XMLConfigBuilder
时没有指定环境配置(id), 则使用xml中的默认配置. 即获取default
属性值. - 遍历子节点, 找到当前环境的配置相同的id. 如果匹配则进行3步骤的解析过程, 否则继续寻找匹配的环境配置节点.
- 环境配置主要包括:
- 事务工厂:
TransactionFactory
transactionManagerElement
方法根据transactionManager
配置, 创建TransactionFactory
对象 - 数据源:
DataSource
dataSourceElement
方法根据dataSource
创建一个数据源工厂对象, 并由该工厂获取数据源.
- 事务工厂:
databaseIdProvider节点
mybatis根据该节点识别数据厂商, 执行sql语句.
MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。
参考配置如下:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
databaseIdProviderElement
方法会解析该节点为Configuration
对象设置databaseId
.
VendorDatabaseIdProvider
是DB_VENDOR
对应的mybatis数据库厂商识别类. 它通过数据源的DatabaseMetaData
对象的getDatabaseProductName
方法获取数据库名称, 与属性中配置的键进行匹配, 如果包含则别名设置为该键的值. 如果 getDatabaseProductName()
返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。
typeHandlers节点
mybatis的类型转换器用于处理JDBC类型和java类型之间的转换逻辑.
参考: mybatis类型转换器
typeHandlerElement
在处理该节点时, 分为两种情况:
- 使用
package
方式配置.
这种方式会加载指定包下的所有实现TypeHandler
接口的子类(推荐重写BaseTypeHandler
).
并获取该类上@MappedJdbcTypes
注解所指定的JdbcType
.
最后通过typeHandlerRegistry.register
完成注册. - 使用
typeHandler
方式配置
这种方式会直接获取javaType
,jdbcType
,handler
属性, 通过反射获取其对应的java类型.
最后通过typeHandlerRegistry.register
完成注册.
所有的TypeHandler
都放到Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
中, java类型和jdbc类型的映射关系, 由Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
管理.TypeHandlerRegistry
初始化时, 会添加默认的类型转换器.
mappers节点
该节点支持使用package
作为子节点配置, 也支持使用子节点使用resource
, url
, class
属性配置资源位置.
1. 使用package配置
配置如下:
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
程序会获取当前包路径下的所有Class, 并依次调用addMapper(Class<T> type)
方法.
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
addMapper
只对接口类型进行处理. 所有处理过的mapper接口都由Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
管理.
hasMapper
用于判断当前接口是否存在于knownMappers
, 存在则不用处理. 不存在则向knownMappers
新增当前类及对应的MapperProxyFactory
(用于动态代理生成实体, 执行接口方法).
之后, 创建MapperAnnotationBuilder
对象用于解析当前mapper接口, parse()
方法为具体的解析逻辑.
2. 使用resource配置
配置如下:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
XMLMapperBuilder
是解析sql xml文件的解析器. 调用parse
方法用于解析xml.
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
3. 使用url配置
解析方式同resource
。
4. 使用class配置
解析方式相当于package
中遍历解析Class的一个环节.
总结
几种解析器:
XMLConfigBuilder
: mapper配置文件xml的解析. 它的parse
方法返回Configuration
对象.XMLMapperBuilder
: xml映射文件的解析.MapperAnnotationBuilder
: 接口映射的解析. 通过Configuration
的addMapper
oraddMapper
调用到.