使用了mybatis这么久还没有具体探究了SpringBoot启动时候对于mybatis是怎么加载的。
1、首先项目构建时我们会引入相关的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
mybatis-spring-boot-starter这个包声明了自动构建MyBatis的一些配置,以及创建SqlSessionFactory的类
1 MybatisAutoConfiguration

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 设置数据源
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
// 加载 MyBatis 全局配置文件 例:如果配置了 mybatis.config-location(如 classpath:mybatis-config.xml),则加载该全局配置文件。
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
// 将 application.yml 中的 MyBatis 配置(以 mybatis.configuration.* 开头的属性)应用到 SqlSessionFactory
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// 注册 MyBatis 插件 例如分页插件
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
// 多数据库支持
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
// 设置类型别名扫描包
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
// 设置类型别名的父类
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
// 设置类型处理器扫描包
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
// 注册自定义类型处理器
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
// 加载sql mapper.xml文件路径 路径通过 mybatis.mapper-locations 配置。
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
// 动态 SQL 语言驱动
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
// 调用 SqlSessionFactoryBean 的 getObject() 方法,生成最终的 SqlSessionFactory 实例。
return factory.getObject();
}
作用
sqlSessionFactory 方法的主要目的是 自动构建并配置 MyBatis 的 SqlSessionFactory 实例,使得开发者无需手动编写大量 XML 或 Java 配置即可集成 MyBatis。该方法通过以下步骤实现:
- 依赖注入关键组件 利用 Spring Boot 的自动装配机制,注入必要的依赖(如 `DataSource、MyBatis 配置属性等)。
- 应用 MyBatis 全局配置 加载 `mybatis-config.xml(如果存在)或通过属性配置 MyBatis 的行为(如缓存、插件等)。
- 配置 Mapper 文件位置 扫描并注册 Mapper 接口或 XML 文件,使其能被 MyBatis 识别。
- 整合 Spring 事务管理
确保 SqlSessionFactory` 与 Spring 的事务管理器兼容。
二、 SqlSessionFactoryBean
factory.getObject() 创建sqlSessionFactory

最终在buildSqlSessionFactory这个方法完成创建。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
// 使用已经有的Configuration 对象
targetConfiguration = this.configuration;
// 合并全局变量(configurationProperties)
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
// 通过 XML 配置文件初始化 Configuration
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
// 创建默认 Configuration 并设置全局变量
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
/**
* 设置核心扩展组件
* ObjectFactory:控制对象(如 POJO)的实例化方式。
* ObjectWrapperFactory:用于包装返回对象(如集合)。
* VFS:虚拟文件系统实现(如处理 Spring Boot 内嵌 JAR 中的资源)
*/
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 扫描包下的类注册别名
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
// 显式注册类型别名
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
// 注册插件
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
// 处理类型处理器
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
// 多数据库支持
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
// 解析 XML 配置文件
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// SpringManagedTransactionFactory 确保 MyBatis 事务由 Spring 管理。
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// 加载和解析 Mapper XML文件
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 构建 SqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
图例解析:

三、 XMLMapperBuilder

XMLMapperBuilder继承于BaseBuilder。他们对于XML文件本身技术上的加载和解析都委托给了XPathParser,最终用的是jdk自带的xml解析器而非第三方比如dom4j,底层使用了xpath方式进行节点解析。new XPathParser(reader, true, props, new XMLMapperEntityResolver())的参数含义分别是Reader,是否进行DTD 校验,属性配置,XML实体节点解析器。
entityResolver比较好理解,跟Spring的XML标签解析器一样,有默认的解析器,也有自定义的比如tx,dubbo等,主要使用了策略模式,在这里mybatis硬编码为了XMLMapperEntityResolver。
XMLMapperEntityResolver的定义如下
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
/*
* Converts a public DTD into a local one
* 将公共的DTD转换为本地模式
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
*
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
try {
if (systemId != null) {
String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
} else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
}
}
return null;
} catch (Exception e) {
throw new SAXException(e.toString());
}
}
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
}
该类的核心作用是通过本地加载 DTD/XSD 验证文件,避免 XML 解析时从网络下载外部资源
继续往下看
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
主要看 createDocument
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
//设置由本工厂创建的解析器是否支持XML命名空间 TODO 什么是XML命名空间
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
//设置是否将CDATA节点转换为Text节点
factory.setCoalescing(false);
//设置是否展开实体引用节点,这里应该是sql片段引用的关键
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
//设置解析mybatis xml文档节点的解析器,也就是上面的XMLMapperEntityResolver
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
主要是根据mybatis自身需要创建一个文档解析器,然后调用parse将输入input source解析为DOM XML文档并返回。
得到XPathParser实例之后,就调用另一个使用XPathParser作为配置来源的重载构造函数了,如下:

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;
}
其中调用了父类BaseBuilder的构造器(主要是设置类型别名注册器,以及类型处理器注册器):
XMLConfigBuilder创建完成之后,SqlSessionFactoryBean调用xmlMapperBuilder.parse();创建Configuration。所有,真正Configuration构建逻辑就在xmlMapperBuilder.parse();里面,如下所示:

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
1. 检查资源是否已加载 (`configuration.isResourceLoaded(resource))
- 作用:
确保同一 Mapper XML 文件不会被重复解析(如多线程环境或错误配置时)。
实现原理:
configuration对象维护了一个Set loadedResources,存储所有已加载的 XML 文件路径(如com/example/UserMapper.xml)
2. 解析根标签 (configurationElement(…))
- 作用
- 解析 XML 文件中标签下的所有配置元素,
包括:- 二级缓存配置
- 结果集映射
- 可重用的 SQL 片段
- :SQL 语句定义
实现逻辑:
- java复制下载
private void configurationElement(XNode context) {
// 获取 namespace 属性(必须对应 Mapper 接口的全限定名)
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"));
// 解析所有 <resultMap>
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析所有 <sql> 片段
sqlElement(context.evalNodes("/mapper/sql"));
// 解析所有 SQL 语句(select|insert|update|delete)
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
3. 标记资源为已加载 ( addLoadedResource )
- 目的:
将当前 XML 文件路径(如 com/example/UserMapper.xml)添加到 `loadedResources 集合,后续再次解析同一文件时会直接跳过。
4. 绑定 Mapper 接口 (bindMapperForNamespace)
- 作用:
将 XML 中 namespace 属性指定的接口(如 com.example.UserMapper`)注册到 MyBatis,使得接口方法与 XML 中的 SQL 语句关联。
实现逻辑:
- java复制下载
private void bindMapperForNamespace() {
// 获取 namespace 对应的接口类
Class<?> boundType = Resources.classForName(namespace);
// 将接口添加到 MapperRegistry
if (boundType != null && !configuration.hasMapper(boundType)) {
configuration.addMapper(boundType);
}
}
- 关键点:
MyBatis 通过动态代理为接口生成实现类,将方法调用映射到 XML 中的 SQL 语句。
5. 处理延迟解析的依赖项
- 背景:
某些配置项(如 `)可能依赖其他尚未解析的资源(如其他 XML 文件中的定义),需要延迟解析。 - 具体方法:
- parsePendingResultMaps()
解析之前未能完全初始化的 ResultMap(例如,引用了其他 ResultMap` 的情况)。 - parsePendingCacheRefs()
处理 标签引用的其他命名空间的缓存配置。 - parsePendingStatements()
完成所有 SQL 语句的最终解析(如确认引用的 resultMap 或 parameterType` 已存在)。
- parsePendingResultMaps()


1176

被折叠的 条评论
为什么被折叠?



