MyBatis 初始化深度解析:从配置文件到 Configuration 构建全流程
MyBatis 的初始化过程本质是 “读取并解析所有配置文件(全局配置 + Mapper 映射),将配置信息组装为 Configuration 核心对象,最终基于该对象创建 SqlSessionFactory” 的过程。本文结合 MyBatis 3.5.x 源码,从 “入口→全局配置解析→Mapper 映射解析→Configuration 组装” 四个维度,完整拆解初始化逻辑,帮你理解每一步配置的作用与组件关联。
初始化核心目标与整体流程
在深入源码前,先明确初始化的核心产出和整体链路,建立宏观认知:
核心目标
- 构建
Configuration 对象:MyBatis 的 “全局配置中心”,存储所有配置信息(数据源、SQL 语句、结果映射、插件、别名等);
- 创建
SqlSessionFactory:基于 Configuration 生成,是后续创建 SqlSession(数据库会话)的工厂,全局唯一。
整体流程链路
graph TD
A[加载 mybatis-config.xml 输入流] --> B[创建 XMLConfigBuilder 解析器]
B --> C[解析全局配置节点 properties/settings/typeAliases等]
C --> D[解析 mappers 节点,加载并解析所有 Mapper.xml]
D --> E[将所有配置信息组装到 Configuration 对象]
E --> F[基于 Configuration 创建 SqlSessionFactory]
初始化入口:SqlSessionFactoryBuilder.build ()
MyBatis 初始化的入口是 SqlSessionFactoryBuilder 的 build() 方法,该方法负责触发配置解析并生成 SqlSessionFactory。
入口源码解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); }
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { } } }
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
|
关键对象:XMLConfigBuilder
XMLConfigBuilder 是 MyBatis 全局配置文件的专用解析器,其核心能力:
- 基于 XPath 解析 XML 文档,生成
XNode(MyBatis 对 XML 节点的封装);
- 按固定顺序解析
mybatis-config.xml 的各个子节点,将解析结果存入 Configuration;
- 保证解析过程的线程安全(
parsed 标记避免重复解析)。
全局配置解析:parseConfiguration () 方法
XMLConfigBuilder.parse() 方法会调用 parseConfiguration(),这是初始化的 “核心枢纽”—— 按顺序解析 mybatis-config.xml 的所有顶层节点,并将配置信息逐一写入 Configuration。
顶层节点解析顺序与源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
|
关键节点解析细节
(1)<properties> 节点:外部属性加载
作用:引入外部 .properties 文件,支持在后续配置中通过 ${key} 引用,解耦数据库配置与 XML。
解析逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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); } }
|
示例:若 db.properties 有 jdbc.url=xxx,则 mybatis-config.xml 中可写 ${jdbc.url} 引用。
(2)<settings> 节点:全局行为控制
作用:配置 MyBatis 核心行为(如缓存、延迟加载、SQL 日志等),是初始化中影响最大的节点之一。
解析逻辑:
1 2 3 4 5 6 7 8
| private void settingsElement(Properties props) { configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); }
|
关键配置:cacheEnabled(二级缓存总开关)、mapUnderscoreToCamelCase(数据库 user_name → 实体 userName)、logImpl(SQL 日志打印)。
(3)<environments> 节点:数据库环境
作用:配置数据库连接池(数据源)和事务管理器,支持多环境(如开发 / 测试 / 生产)切换。
解析逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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()); } } } }
|
核心:Environment 对象包含 TransactionFactory(事务工厂)和 DataSource(数据源),是后续创建 SqlSession 时获取数据库连接的关键。
(4)<mappers> 节点:Mapper 映射加载
作用:指定 Mapper.xml 文件或 Mapper 接口的位置,是初始化中 “连接 SQL 与 Java 接口” 的关键步骤。
解析逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| private void mapperElement(XNode context) throws Exception { if (context != null) { XNode packageNode = context.evalNode("package"); if (packageNode != null) { String mapperPackage = packageNode.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { for (XNode child : context.getChildren()) { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { 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) { 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) { 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."); } } } } }
|
关键:无论哪种方式,最终都会创建 XMLMapperBuilder 解析 Mapper.xml,将 SQL 语句、ResultMap 等配置存入 Configuration。
Mapper.xml 解析:XMLMapperBuilder 的核心工作
mappers 节点解析后,会触发 XMLMapperBuilder.parse() 方法,专门处理 Mapper.xml 的内部节点(如 <resultMap>、<select>、<insert> 等),这是初始化中最复杂的环节之一。
1. Mapper 解析入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); }
parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
|
2. Mapper 核心节点解析:configurationElement ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
|
(1)<resultMap> 解析:结果映射核心
resultMap 是 MyBatis 解决 “数据库字段名与实体属性名不一致” 和 “复杂关联查询” 的关键,解析后生成 ResultMap 对象存入 Configuration。
核心源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) { String type = resultMapNode.getStringAttribute("type"); Class<?> typeClass = resolveClass(type); List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings); for (XNode resultChild : resultMapNode.getChildren()) { if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); ResultMapResolver resolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resolver); throw e; } }
|
关键:ResultMap 存储了 “数据库列名→实体属性名” 的映射规则,以及关联查询(association/collection)的配置,是后续 ResultSet 封装为实体的核心依据。
(2)SQL 节点解析:构建 MappedStatement
<select>/<insert> 等节点解析后会生成 MappedStatement 对象,该对象封装了单条 SQL 的完整信息(SQL 语句、参数类型、结果类型、缓存配置等),是后续执行 SQL 的 “元数据”。
核心逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder( configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
|
核心:MappedStatement 的 id 为 namespace + 节点id(如 com.xxx.UserMapper.selectById),后续通过该 id 可从 Configuration 中找到对应的 SQL 语句。
初始化收尾:Configuration 组装与 SqlSessionFactory 创建
当所有配置(全局配置 + Mapper 映射)解析完成后,Configuration 对象已包含 MyBatis 运行所需的全部信息,此时 SqlSessionFactoryBuilder 会基于该对象创建 SqlSessionFactory 的默认实现 DefaultSqlSessionFactory。
1. Configuration 的核心内容
Configuration 是 MyBatis 的 “大脑”,存储的关键信息包括:
| 存储内容 |
对应配置节点 / 解析逻辑 |
作用 |
environment |
<environments> 节点 |
数据库环境(数据源 + 事务管理器) |
mappedStatements |
Mapper.xml 的 <select>/<insert> 等节点 |
封装所有 SQL 语句的元数据 |
resultMaps |
Mapper.xml 的 <resultMap> 节点 |
结果映射规则 |
typeAliasRegistry |
<typeAliases> 节点 |
类型别名注册表(简化类名引用) |
plugins |
<plugins> 节点 |
注册的插件(拦截器链) |
cacheEnabled |
<settings> 节点的 cacheEnabled 属性 |
二级缓存总开关 |
mapUnderscoreToCamelCase |
<settings> 节点的对应属性 |
驼峰命名自动映射开关 |
2. SqlSessionFactory 的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; }
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } }
|
关键:DefaultSqlSessionFactory 仅持有 Configuration 对象,后续创建 SqlSession 时,会基于 Configuration 中的 environment 获取数据源和事务管理器,创建 Executor(执行器)。
初始化关键注意事项
- 配置解析顺序不可乱:
parseConfiguration() 中节点解析顺序有依赖关系(如 <properties> 先解析,后续节点才能引用 ${key};<settings> 后应用,因可能依赖 objectFactory 等组件);
- 延迟解析机制:若 Mapper 解析时依赖未完成的组件(如
resultMap 继承未解析的父 resultMap),会加入延迟队列(incompleteResultMaps/incompleteStatements),待依赖解析完成后再处理;
- 线程安全:
XMLConfigBuilder 的 parsed 标记确保每个解析器仅解析一次,避免多线程重复解析;
- 配置覆盖:外部传入的
properties(如 build() 方法参数)会覆盖内部 <property> 节点的配置,优先级为:外部参数 > 外部文件 > 内部节点。
总结
MyBatis 的初始化过程是 “配置驱动” 的典型案例,核心流程可概括为:
- 入口触发:通过
SqlSessionFactoryBuilder.build() 加载配置文件输入流;
- 全局解析:
XMLConfigBuilder 按顺序解析 mybatis-config.xml 的顶层节点,组装基础配置到 Configuration;
- Mapper 解析:
XMLMapperBuilder 解析 Mapper.xml,将 SQL 语句、ResultMap 等封装为 MappedStatement/ResultMap,存入 Configuration;
- 工厂创建:基于完整的
Configuration 创建 SqlSessionFactory,初始化完成