mybatis-初始化(一)mybatis-confg解析

    技术2022-07-13  117

    概述

    mybatis初始化主要包含mybatis全局配置加载、解析,mapper文件加载解析。本篇主要是对mybatis配置文件解析进行解读。

    先看下mybatis-config.xml有些啥内容

    configuration(配置) properties(属性)settings(设置)typeAliases(类型别名)typeHandlers(类型处理器)objectFactory(对象工厂)plugins(插件)environments(环境配置) environment(环境变量) transactionManager(事务管理器)dataSource(数据源) databaseIdProvider(数据库厂商标识)mappers(映射器)

    每节对应一个xml node。开干!

    解析入口

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); /* * 1. 执行xml解析 * 2. 解析后的config,创建SqlSessionFactory */ return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }

    在SqlSessionFactoryBuilder.build()中,解析完Configuration直接创建SqlSessionFactory。

    configuration解析

    mybatis-config.xml的输入流已经在XPathParser里了。

    public class XMLConfigBuilder extends BaseBuilder { /** * 是否已经解析 */ private boolean parsed; /** * Xpath解析器 */ private final XPathParser parser; /** * 当前应用的环境id */ private String environment; 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; } }

    xml配置构造器存储XPath解析器,主要用它来完成xml相关的解析。

    解析方法如下:

    public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //解析 configuration节点,configuration是根节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } /** * 解析config.xml * @param root */ private void parseConfiguration(XNode root) { try { // 解析<properties/> propertiesElement(root.evalNode("properties")); // 解析<settings /> Properties settings = settingsAsProperties(root.evalNode("settings")); // vfs实现类处理 loadCustomVfs(settings); // 日志实现类处理 loadCustomLogImpl(settings); // 解析<typeAliases /> typeAliasesElement(root.evalNode("typeAliases")); // 解析<plugins /> pluginElement(root.evalNode("plugins")); // 解析<objectFactory /> objectFactoryElement(root.evalNode("objectFactory")); // 解析<objectWrapperFactory /> objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析<reflectorFactory /> reflectorFactoryElement(root.evalNode("reflectorFactory")); // Configuration属性设置 settingsElement(settings); // 解析<environments /> environmentsElement(root.evalNode("environments")); // 解析<databaseIdProvider /> databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析<typeHandlers /> typeHandlerElement(root.evalNode("typeHandlers")); // 解析<mappers /> mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

    从上到下按照针对每个子xml node挨个解析,很清晰。

    让我们挨个看,重要的重点看,不重要的扫一眼。

    properties解析

    properties主要是为了解决占位符替换(${}),可以自定义变量,也可以外部引入。涉及到xml相关的知识不展开,主要是把解析好的properties存储到parser和Configuration中,后续使用。

    private void propertiesElement(XNode context) throws Exception { if (context != null) { //直接使用XNode提供的方法,将node解析成Properties Properties defaults = context.getChildrenAsProperties(); // 引用外部properties解析支持 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); } // 将解析好的 properties放到解析器中,进行后续动态值替换 parser.setVariables(defaults); configuration.setVariables(defaults); } }

    settings解析

    settings也当做properties处理。只看下解析逻辑,具体每个配置有什么作用,可以去官网参考下。

    <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> private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } // 将settings转换成properties Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); // 遍历通过setter方法判断是否存在非法属性 for (Object key : props.keySet()) { 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; }

    TypeAlias解析

    逻辑较简单,具体别名处理在type中的alias已经讲过了

    private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // 大多数情况使用指定package的方式 if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); // 通过typeAliasRegistry提供的批量注册功能,注册该包下的所有类 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); } } } }

    plugins解析

    这里先了解下 插件是如何解析和注册的。插件的具体内容将在mybatis整体介绍完之后再讲解。

    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); } } }

    对象工厂

    支持指定对象工厂,可以自定义对象创建过程,就是入参、出参。

    private void objectFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties properties = context.getChildrenAsProperties(); // 别名 解析类型,创建实例 ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance(); // 设置属性 factory.setProperties(properties); // 添加Object工厂 configuration.setObjectFactory(factory); } }

    对象包装器工厂

    用的少,略过

    private void objectWrapperFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance(); configuration.setObjectWrapperFactory(factory); } }

    反射器工厂

    用的少,略过

    private void reflectorFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance(); configuration.setReflectorFactory(factory); } }

    默认配置

    默认配置设置

    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"))); }

    环境信息解析

    <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>

    多环境一般不使用mybatis的多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)) { // 解析<transactionManager />标签 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 解析<dataSource />标签 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()); } } } }

    数据库厂商id解析

    用的少,略过

    private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility if ("VENDOR".equals(type)) { type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }

    类型处理器解析

    即可指定包名批量注册,又可以单个注册。

    private void typeHandlerElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); // 通过包名批量注册typeHandler typeHandlerRegistry.register(typeHandlerPackage); } else { String javaTypeName = child.getStringAttribute("javaType"); String jdbcTypeName = child.getStringAttribute("jdbcType"); String handlerTypeName = child.getStringAttribute("handler"); Class<?> javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<?> typeHandlerClass = resolveClass(handlerTypeName); if (javaTypeClass != null) { if (jdbcType == null) { typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { typeHandlerRegistry.register(typeHandlerClass); } } } } }

    mapper映射文件解析

    重点,mapper映射文件就是我们写sql的文件。解析映射文件,然后和Mapper接口建立映射。

    看下mapper的配置内容实例:

    <!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>

    共有4中方式配置mapper.xml,看下代码如何解析

    private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { // 通过包名批量注册 String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // 通过resource外部引入的需要单独解析 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // 通过url外部引入的需要单独解析 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 通过Mapper接口的相同路径尝试加载 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }

    这里分两种情况,

    一:配置的是Mapper Class,包名和class,是先去加载mapper接口,然后再当前路径将.java替换成.xml尝试加载。

    二:直接配置的Mapper.xml,那么委托给XMLMapperBuilder进行具体内容解析。

    Mapper.xml的内容具体比较复杂,单独放在一篇文章进行解析。

    总结

    mybatis-config的解析比较简单,只要可以对着官网的文档,先看配置的内容和格式,再看代码如何解析,按部就班看几眼就能懂。

    Processed: 0.015, SQL: 9