一、Configuration
Configuration对应的就是mybatis的配置文件,是mybatis的核心类之一,影响着mybatis的各种设置和运行行为,运行mybatis的第一步就是把配置文件转换成Configuration对象。
回到SqlSessionFactoryBuilder的build方法,configuration配置文件的解析是通过XMLConfigBuilder来实现。调用其parse()方法返回Configuration对象。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//parser=XPathParser
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//会把所有的properties的属性值存入的Configuration的variables属性
propertiesElement(root.evalNode("properties"));
//读取settings
Properties settings = settingsAsProperties(root.evalNode("settings"));
//配置自定义的VFS实现类,多个以逗号分隔
loadCustomVfs(settings);
//设置日志的具体实现这些都在官网可查
loadCustomLogImpl(settings);
//把类型别名添加到typeAliasRegistry属性中
typeAliasesElement(root.evalNode("typeAliases"));
//把插件添加到interceptorChain属性中
pluginElement(root.evalNode("plugins"));
//设置objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//设置objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//设置reflectorFactory
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//配置所有的settings值
settingsElement(settings);
//解析environments
environmentsElement(root.evalNode("environments"));
//设置数据库提供商标识
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//把类型处理器注册到typeHandlerRegistry
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers文件具体的解析过程是交给XMLMapperBuilder来实现
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这里就是一步步去解析配置文件的过程。
propertiesElement(root.evalNode(“properties”)):
从配置来看,有三种方式,一是设置resource或者url属性文件路径,还有就是直接设置键值对,resources和url只能二选一,不能同时配置,所以该方法会把这几种属性配置方式的配置的值都解析成一个Properties对象,然后设置到Configuration对象的variables属性。
loadCustomVfs(settings):
会根据settings配置的vfsImpl值(类的全限定名多个以逗号隔开),转换成class对象,然后添加到mybatis自动的VSF列表中。
loadCustomLogImpl(settings):
配置mybatis日志的具体实现,可取值SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING,Configuration对象会默认添加这些类型别名到typeAliasRegistry中,所以该方法就是根据你配置的日志实现找到具体class然后设置系统所用的日志实现类。
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);
typeAliasesElement(root.evalNode(“typeAliases”)):
解析类型别名到typeAliasRegistry,意思就是我们用了这个类型别名之后 就可以在需要用这个类的时候,直接用别名就可以了,而不需要去写他的全限定名,我们也可以直接配置一个package,这样mybatis就会自动到包名下去搜索。
pluginElement(root.evalNode(“plugins”)):
源码逻辑很简单就是把配置好的interceptor实例化,然后添加到interceptorChain对象中保存到interceptors数组中,这里需要注意的是这些都是mybatis中对插件的配置。
插件就是在mybatis执行映射语句的时候对一些方法进行拦截调用,也就是你可以针对你需要拦截的方法编写对应的插件,只需要实现Interceptor 接口,然后配置好对应的需要拦截的方法签名即可,官网有样例。mybatis默认可以拦截的方法有:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
objectFactoryElement(root.evalNode(“objectFactory”)):
配置ObjectFactory,他的默认实现是DefaultObjectFactory,如果你想修改一些实例的属性值,可以实现这个接口然后在配置文件中配置好,然后就可以操作。Configuration会设置到objectFactory属性。
objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)):
这是对象包装工厂,一般不怎么使用,除非你有特殊类需要特殊处理的时候,才会用到。
reflectorFactoryElement(root.evalNode(“reflectorFactory”)):
设置反射工厂,可以替代默认的实现
settingsElement(settings):
设置好各种配置,具体配置参数的含义在官网都有介绍。
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
}
environmentsElement(root.evalNode(“environments”)):
解析Environment对象
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)) {
//获取JDBC或者MANAGED事务工厂,这已经分析过
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
/**获取数据源的工厂,根据<dataSource type="POOLED">配置type类型
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
找到对应的 数据源工厂,并且获取到dataSource标签下所有的属性,并转换成Properties对象**/
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());
break;
}
}
}
}
DataSourceFactory有三个实现,分别是JndiDataSourceFactory、PooledDataSourceFactory、UnpooledDataSourceFactory,其中PooledDataSourceFactory继承自UnpooledDataSourceFactory,都是为了得到DataSource实例。简单分析一下UnpooledDataSourceFactory:
从dataSourceElement方法可知,会调用DataSourceFactory的setProperties方法:
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
//把数据源封装成元数据对象
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
//接下来就是通过元数据对象来设置配置的属性到原对象中,也就是调用其setter方法
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
上边的方法借助MetaObject 对象通过反射的方式把数据库连接的url、driver、username等信息配置到对应的属性。
注意:在配置environments时,可以配置多个数据源,但是具体使用哪个数据源是根据配置的environment id="development"和environments default="development"两个配置的值相等即可
databaseIdProviderElement(root.evalNode(“databaseIdProvider”)):
解析数据库提供者标签,可以通过标签来配置,Configuration对象中实际保存的是databaseId字符串,也就是会根据数据源得到当前使用的数据库产品,比如mysql、oracle等。
释义:不同的数据库提供商,对于语句的执行方面也会有差异,比如分页的实现、时间函数等操作,所以在编写映射文件(mapper.xml)的时候就会存在这种问题,同一个操作(比如都是查询时间)但是不同数据库语句不一样,所以此时我们可以在映射文件中添加databaseId属性,来标识不同的数据库,如果配置文件中配置了databaseIdProvider,mybatis就会加载所有不带带和匹配带有databaseId的语句,如果带和不带都有,则不带会被舍弃(也就是语句中的databaseId和当前数据库匹配上后,不带databaseId的才会被舍弃)。
typeHandlerElement(root.evalNode(“typeHandlers”)):
类型处理器:主要是在PreparedStatement对参数或结果集处理的时候,用类型转换器转换成对应的Java类型,所以客户可以编写自己的typehandler来处理对应的数据类型,最后都会被注册到Configuration 的typeHandlerRegistry中。
mapperElement(root.evalNode(“mappers”)):
加载mapper文件,配置文件中可以指定一个包名,或者文件路径以及类的全限定名等多种方式。如果采用文件路径配置方式,则会交给XMLMapperBuilder来解析,其余两种都是直接调用addMapper方法或者addMappers方法,最终都会代用MapperRegistry的addMapper方法。
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 {
//添加到map集合中,键是mapper接口,值是其代理工厂类,记住即可
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();//解析xml文件以及注解方式
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
mapper.xml映射文件的解析过程单独继续分析。
二、总结
Configuration对象会把配置文件的各种信息解析成java对象,以供后续mybatis的调用,这也是mybatis的核心,所以有必要知道每个配置在Configuration对象中的存在形式,方便后续源码分析的时候能更加简单。
以上,有任何不对的地方,请指正,敬请谅解。