0%

mybatis初始化

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 初始化的入口是 SqlSessionFactoryBuilderbuild() 方法,该方法负责触发配置解析并生成 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
// org.mybatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
// 调用重载方法,默认环境(environment=null)、无额外属性(properties=null)
return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 1. 创建 XML 配置解析器(XMLConfigBuilder),核心职责是解析 mybatis-config.xml
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

// 2. 解析配置文件,生成 Configuration 对象;再基于 Configuration 创建 SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset(); // 重置错误上下文
try {
inputStream.close(); // 关闭配置文件输入流
} catch (IOException e) {
// 忽略流关闭异常,优先抛出配置解析异常
}
}
}

// 最终创建 SqlSessionFactory 的默认实现(DefaultSqlSessionFactory)
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
// org.apache.ibatis.builder.xml.XMLConfigBuilder
private void parseConfiguration(XNode root) {
try {
// 1. 解析 <properties> 节点:加载外部属性文件(如 db.properties)
propertiesElement(root.evalNode("properties"));

// 2. 解析 <settings> 节点:全局设置(如缓存开关、驼峰映射)
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings); // 加载自定义 VFS(虚拟文件系统)
loadCustomLogImpl(settings); // 加载自定义日志实现

// 3. 解析 <typeAliases> 节点:注册类型别名(简化类名引用)
typeAliasesElement(root.evalNode("typeAliases"));

// 4. 解析 <plugins> 节点:注册插件(如分页插件、日志插件)
pluginElement(root.evalNode("plugins"));

// 5. 解析 <objectFactory> 节点:自定义对象工厂(创建实体对象的工厂)
objectFactoryElement(root.evalNode("objectFactory"));

// 6. 解析 <objectWrapperFactory> 节点:自定义对象包装工厂(包装实体对象)
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

// 7. 解析 <reflectorFactory> 节点:自定义反射工厂(优化反射性能)
reflectorFactoryElement(root.evalNode("reflectorFactory"));

// 8. 应用 <settings> 配置到 Configuration(需在 objectFactory 等之后,因可能依赖这些组件)
settingsElement(settings);

// 9. 解析 <environments> 节点:数据库环境(数据源 + 事务管理器)
environmentsElement(root.evalNode("environments"));

// 10. 解析 <databaseIdProvider> 节点:数据库厂商标识(多数据库适配)
databaseIdProviderElement(root.evalNode("databaseIdProvider"));

// 11. 解析 <typeHandlers> 节点:注册类型处理器(Java 类型 ↔ JDBC 类型转换)
typeHandlerElement(root.evalNode("typeHandlers"));

// 12. 解析 <mappers> 节点:加载并解析所有 Mapper.xml(核心,关联 SQL 与映射规则)
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) {
// 1. 解析 <properties> 节点内部的 <property> 子节点
Properties defaults = context.getChildrenAsProperties();
// 2. 获取 <properties> 的 resource 或 url 属性,加载外部文件
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));
}
// 3. 合并外部传入的 properties(如 build() 方法的参数)
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 4. 将合并后的 properties 存入 XPathParser 和 Configuration
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}

示例:若 db.propertiesjdbc.url=xxx,则 mybatis-config.xml 中可写 ${jdbc.url} 引用。

(2)<settings> 节点:全局行为控制

作用:配置 MyBatis 核心行为(如缓存、延迟加载、SQL 日志等),是初始化中影响最大的节点之一。
解析逻辑:

1
2
3
4
5
6
7
8
private void settingsElement(Properties props) {
// 从 props 中读取配置,设置到 Configuration 对应属性
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); // 二级缓存开关,默认true
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); // 驼峰映射,默认false
configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); // 日志实现(如 STDOUT_LOGGING)
// ... 其他设置(如 lazyLoadingEnabled、aggressiveLazyLoading 等)
}

关键配置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"); // 默认环境 ID
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id"); // 环境 ID
if (isSpecifiedEnvironment(id)) {
// 1. 解析事务管理器(JDBC/MANAGED)
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 2. 解析数据源(POOLED/UNPOOLED/JNDI)
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 3. 构建 Environment 对象,存入 Configuration
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) {
// 方式1:通过 <package> 扫描包下所有 Mapper 接口(推荐)
XNode packageNode = context.evalNode("package");
if (packageNode != null) {
String mapperPackage = packageNode.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 方式2:通过 <mapper resource="xxx.xml"/> 加载单个文件
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) {
// 加载类路径下的 Mapper.xml
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse(); // 解析 Mapper.xml
} else if (resource == null && url != null && mapperClass == null) {
// 加载 URL 路径下的 Mapper.xml
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 接口(无 XML,依赖注解)
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
// org.apache.ibatis.builder.xml.XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 1. 解析 Mapper.xml 的核心节点(namespace、cache、resultMap、SQL 语句等)
configurationElement(parser.evalNode("/mapper"));
// 2. 标记该 Mapper 已加载,避免重复解析
configuration.addLoadedResource(resource);
// 3. 将 Mapper 接口与 namespace 绑定(生成动态代理的基础)
bindMapperForNamespace();
}

// 处理延迟解析的节点(如依赖其他 ResultMap 的节点)
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 {
// 1. 解析 <mapper> 的 namespace 属性(必须与 Mapper 接口全类名一致)
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); // 绑定当前 namespace 到辅助类

// 2. 解析 <cache-ref> 节点:引用其他 Mapper 的缓存
cacheRefElement(context.evalNode("cache-ref"));

// 3. 解析 <cache> 节点:配置当前 Mapper 的二级缓存
cacheElement(context.evalNode("cache"));

// 4. 解析 <parameterMap> 节点:参数映射(已过时,推荐用 @Param 或实体类)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));

// 5. 解析 <resultMap> 节点:结果映射(核心,解决字段与属性名不一致、关联查询)
resultMapElements(context.evalNodes("/mapper/resultMap"));

// 6. 解析 <sql> 节点:SQL 片段(复用重复 SQL,如查询字段列表)
sqlElement(context.evalNodes("/mapper/sql"));

// 7. 解析 <select>/<insert>/<update>/<delete> 节点:封装 SQL 语句为 MappedStatement
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) {
// 1. 获取 resultMap 的 type 属性(实体类全类名/别名),解析为 Class 对象
String type = resultMapNode.getStringAttribute("type");
Class<?> typeClass = resolveClass(type);

// 2. 解析子节点:constructor(构造器注入)、discriminator(鉴别器)、id/result(字段映射)
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())) {
// 解析鉴别器(根据字段值动态选择 ResultMap)
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 解析 id(主键)或 result(普通字段),添加 ResultFlag 标记
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID); // 主键标记,优化缓存和嵌套查询
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}

// 3. 处理 resultMap 继承(extends 属性)和自动映射(autoMapping)
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

// 4. 构建 ResultMap 对象,存入 Configuration(或延迟解析)
ResultMapResolver resolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resolver.resolve();
} catch (IncompleteElementException e) {
// 若依赖的 ResultMap 未解析完成,加入延迟队列
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) {
// 创建 StatementHandler 辅助类,解析 SQL 节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(
configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 SQL 节点,生成 MappedStatement 并存入 Configuration
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 延迟解析(如依赖未解析的 ResultMap)
configuration.addIncompleteStatement(statementParser);
}
}
}

核心MappedStatementidnamespace + 节点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
// org.mybatis.session.SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

// org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}

// 后续通过 openSession() 方法创建 SqlSession,依赖 Configuration 中的环境配置
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// ... 其他 openSession() 重载方法
}

关键DefaultSqlSessionFactory 仅持有 Configuration 对象,后续创建 SqlSession 时,会基于 Configuration 中的 environment 获取数据源和事务管理器,创建 Executor(执行器)。

初始化关键注意事项

  1. 配置解析顺序不可乱parseConfiguration() 中节点解析顺序有依赖关系(如 <properties> 先解析,后续节点才能引用 ${key}<settings> 后应用,因可能依赖 objectFactory 等组件);
  2. 延迟解析机制:若 Mapper 解析时依赖未完成的组件(如 resultMap 继承未解析的父 resultMap),会加入延迟队列(incompleteResultMaps/incompleteStatements),待依赖解析完成后再处理;
  3. 线程安全XMLConfigBuilderparsed 标记确保每个解析器仅解析一次,避免多线程重复解析;
  4. 配置覆盖:外部传入的 properties(如 build() 方法参数)会覆盖内部 <property> 节点的配置,优先级为:外部参数 > 外部文件 > 内部节点。

总结

MyBatis 的初始化过程是 “配置驱动” 的典型案例,核心流程可概括为:

  1. 入口触发:通过 SqlSessionFactoryBuilder.build() 加载配置文件输入流;
  2. 全局解析XMLConfigBuilder 按顺序解析 mybatis-config.xml 的顶层节点,组装基础配置到 Configuration
  3. Mapper 解析XMLMapperBuilder 解析 Mapper.xml,将 SQL 语句、ResultMap 等封装为 MappedStatement/ResultMap,存入 Configuration
  4. 工厂创建:基于完整的 Configuration 创建 SqlSessionFactory,初始化完成

欢迎关注我的其它发布渠道