文章目录
- properties
- setting
- typeAliases
- plugins
- objectFactory
- objectWrapperFactory
- reflectorFactory
- environments
- databaseIdProvider
- typeHandlers
- mappers
作用
XMLConfigBuilder 的作用是解析mybatis-config.xml配置文件,它是在SqlSessionFactoryBuilder被初始化的,然后调用XMLConfigBuilder 对象的parse 方法开始解析配置文件。
构造方法
XMLConfigBuilder 继承了BaseBuilder,它有七个构造方法,
但最终调用的是XMLConfigBuilder(XPathParser parser, String environment, Properties props)
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; }
配置解析
XMLConfigBuilder 真正开始进行配置解析的 ,parse()方法是解析的开始,在方法parseConfiguration(XNode root) 进行各个节点的解析
parse()
public Configuration parse() { // 已经解析过不能再次解析 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
parseConfiguration
/** * 解析配置文件 * @param root */ private void parseConfiguration(XNode root) { try { // issue #117 read properties first // 解析properties 节点 propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // 解析 typeAliases 标签 typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析环境environments 标签 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 解析SQL 文件 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
标签解析
配置文件的标签如下:
configuration 配置
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
以上每个标签都有各自的作用
properties
properties 可以配置一些属性值,然后在配置文件中动态替换。在配置文件中是下面这样的
<properties resource="db.properties"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> </properties>
解析步骤:
1、如果有properties标签,先把properties标签下的子标签property进行解析,name作为key,value 作为value,以键值对的形式返回到java.util.Properties 对象中,defaults接收
2、解析properties 标签属性,resource 和 url ,二者只能配置一个,否则会报错,
3、把 配置文件中的属性解析出来,然后放到defaults 中,由于是hashtable存储,所以会覆盖掉property属性中相同key的值
4、把解析到的key-value 设值到XMLConfigBuilder的parser属性的variables和configuration对象的variables属性中以便后续进行属性替换
/** * 解析properties 标签中的变量 * 并最终把解析到的变量放到configuration 中的variables字段中 * @param context * @throws Exception */ private void propertiesElement(XNode context) throws Exception { if (context != null) { // 解析默认的property 标签 Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); // resource 和 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); } }
setting
setting 是MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,有关setting的标签可以查看 官网有详细的介绍,这里主要是看下源码
setting的加载有四个步骤:
1、判断setting 子标签中的name属性对应的值是否是Configuration的属性,不是则抛异常
/** * 判断setting 子标签中的name属性对应的值是否是Configuration的属性,不是则抛异常 * @param context * @return */ private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { // 判断是否有set 方法 if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } return props; }
2、设值自定义的VFS 实现类并设值到configuration对象中
private void loadCustomVfs(Properties props) throws ClassNotFoundException { String value = props.getProperty("vfsImpl"); if (value != null) { String[] clazzes = value.split(","); for (String clazz : clazzes) { if (!clazz.isEmpty()) { @SuppressWarnings("unchecked") Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz); configuration.setVfsImpl(vfsImpl); } } } }
3、加载自定义的日志实现类并设值到configuration对象中
private void loadCustomLogImpl(Properties props) { Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); }
4、将setting中其他配置加载并设值到configuration对象中
/** * setting 属性值设值到configuration对象中 * @param props */ 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"))); }
typeAliases
使用 typeAliases 标签给解析到的类起一个别名,或者也可以自己给类自定义别名。
使用方法如下:
<typeAliases> <typeAlias alias="User" type="entity.UserInfo"/> <!-- <package name="entity.UserInfo"/>--> </typeAliases>
解析的方法是typeAliasesElement
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
该方法会解析typeAliases 下的所有标签元素如果解析到package标签,它会package标签下指定的包下的所有类都给注册到configuration#typeAliasRegistry别名注册表中,注册时会把类名全部转成小写字母然后作为key,类权限定名作为value,注册到注册表中。
如果解析到typeAlias标签,会获取alias和type 属性,如果alias属性没有会判断type 类上是否有Alias 注解,如果有就用Alias注解中的表名作为key,没有类名转为小写字母注册。
注册源码如下:
public void registerAlias(Class<?> type) { String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); }
两个标签最终都会调用TypeAliasRegistry#registerAlias(String alias, Class<?> value)方法
public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 String key = alias.toLowerCase(Locale.ENGLISH); if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) { throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'."); } typeAliases.put(key, value); }
注意:
package 和typeAlias 这两个标签最好不要同时使用,如果同一个类被注册两次是会抛异常的,所以最好不要重复扫描。