一、MyBatis 的产生背景
MyBatis 主要为了解决传统 JDBC 编程中的以下痛点:
- 繁琐的样板代码:JDBC 需要大量重复代码(获取连接、创建语句、处理结果集等)
java
public class JdbcExample {
public User getUserById(int id,int age) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
User user = null;
try {
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis","root", "password");
// 3. 创建PreparedStatement
String sql = "SELECT id, name, age FROM user WHERE id = ? AND age = ?";
ps = conn.prepareStatement(sql);
// 4. 设置参数
ps.setInt(1, id);
ps.setInt(2, age);
// 5. 执行查询
rs = ps.executeQuery();
// 6. 处理结果集
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7. 关闭资源
try {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return user;
}
}
- SQL 与 Java 代码混杂:业务逻辑中硬编码 SQL 语句,难以维护
- 结果集映射复杂:手动将 ResultSet 转换为 Java 对象繁琐易错
- 缺乏缓存机制:需要手动实现查询结果缓存
二、MyBatis 的核心优点
- SQL 与代码分离:SQL 写在 XML 中,与 Java 代码解耦
- 自动对象映射:自动将结果集映射到 POJO,支持复杂映射
- 动态 SQL:提供强大的动态 SQL 功能,避免拼接 SQL 字符串
- 插件机制:可通过插件扩展功能(如分页、性能监控)
- 轻量级:不依赖容器,配置简单,学习曲线平缓
三、MyBatis 核心类
核心类 | 职责 |
---|---|
SqlSessionFactoryBuilder | 构建 SqlSessionFactory,解析配置文件 |
SqlSessionFactory | 创建 SqlSession 的工厂,全局单例 |
SqlSession | 核心会话类,提供 CRUD API |
Executor | SQL 执行器,负责 SQL 的生成和缓存维护 |
MappedStatement | 封装 SQL 语句、输入输出参数等信息 |
Configuration | 所有配置信息的容器,MyBatis 的核心 |
MapperProxy | 动态代理实现,将接口方法调用转为数据库操作 |
四、MyBatis 运行流程分析
代码结构:

实体类:
java
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Date time;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + ''' +
", time=" + time +
'}';
}
}
Mapper接口:
java
public interface UserMapper {
List<User> getUser();
User getUserById(@Param("id") Integer id);
}
Mapper.xml:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.mapper.UserMapper">
<cache></cache>
<select id="getUser" resultType="com.demo.entity.User">
select *
from user
</select>
<select id="getUserById" resultType="com.demo.entity.User">
select *
from user
<where>
<if test="id != null and id != ''">
and id = #{id}
</if>
</where>
</select>
</mapper>
db.properties:
json
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://A.A.A.A:AAAA/数据库?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
db.username=username
db.password=password
mybatis-config.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties" />
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 配置mybatis的环境信息 -->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="jdbc" />
<!-- 配置数据源,采用dbcp连接池 -->
<dataSource type="pooled">
<property name="driver" value="${db.driver}" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml" />
</mappers>
</configuration>
测试类:
java
public class Test {
public static void main(String[] args) {
String configFile = "mybatis-config.xml";
try (
// 1. 读取配置文件
Reader reader = Resources.getResourceAsReader(configFile)) {
// 2. 解析所有XML配置文件(setting、plugin、Mapper映射文件、)
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 3. 获取数据源执行器
SqlSession sqlSession = sessionFactory.openSession();
// 4. 获取Mapper代理对象
UserMapper inventoryMapper = sqlSession.getMapper(UserMapper.class);
// 5.调用mapper的方法
User user = inventoryMapper.getUserById(2);
System.out.println(user);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
1、初始化阶段
(1)读取配置文件
java
// 1. 读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
java
public static Reader getResourceAsReader(String resource) throws IOException {
InputStreamReader reader;
// 判断是否指定了字符集
if (charset == null) {
// 创建InputStreamReader
reader = new InputStreamReader(getResourceAsStream(resource));
} else {
reader = new InputStreamReader(getResourceAsStream(resource), charset);
}
//将 XML 配置文件封装成一个 Reader 读取器对象
return reader;
}
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream((ClassLoader)null, resource);
}
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
// 通过ClassLoaderWrapper获取资源流
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
} else {
return in;
}
}
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return this.getResourceAsStream(resource, this.getClassLoaders(classLoader));
}
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
ClassLoader[] var3 = classLoader;
int var4 = classLoader.length;
// 遍历传入的类加载器数组
for(int var5 = 0; var5 < var4; ++var5) {
ClassLoader cl = var3[var5];
if (null != cl) {
//加载指定路径文件流
InputStream returnValue = cl.getResourceAsStream(resource);
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}

第一步总结:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
主要是通过ClassLoader.getResourceAsStream()方法获取指定的classpath路径下的Resource
(2)构建SqlSessionFactory
java
// 2. 解析所有XML配置文件(setting、plugin、Mapper映射文件、)
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactoryBuilder().build(reader) 构造者模式
将封装好的reader读取器 构建成 SqlSessionFactory,解析配置文件
java
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 会创建一个专门用于解析单个mybatis-config.xml文件的解析器实例
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 调用 .parser() 方法解析XML文件中的各个节点
var5 = this.build(parser.parse());
} catch (Exception var14) {
Exception e = var14;
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException var13) {
}
}
return var5;
}
XMLConfigBuilder 会创建一个专门用于解析单个mybatis-config.xml文件的解析器实例
然后调用 .parser() 方法解析XML文件中的各个节点
那么 .parser() 方法 中做了哪些事?
java
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
// 解析 XML 文件中 /configuration 文件下的各个节点
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
this.parser.evalNode("/configuration") 会将XML中的configuration 解析出来

this.parseConfiguration()解析configuration中的各个节点(properties、plugins等)
java
// 解析各个节点
// 里面的各个方法都会把解析好的属性设置到configuration成员变量里面
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfsImpl(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginsElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlersElement(root.evalNode("typeHandlers"));
this.mappersElement(root.evalNode("mappers"));
} catch (Exception var3) {
Exception e = var3;
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
如:this.propertiesElement(root.evalNode("properties"));
解析properties节点 将解析出来的内容塞到 configuration 属性中
configuration 承载了整个框架运行所需的所有配置信息,贯穿 MyBatis 整个运行周期,启动时解析并缓存所有配置,运行时直接使用,避免每次操作都重新读取配置文件
java
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.");
} else {
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
this.configuration.setVariables(defaults);
}
}
}

this.mappersElement(root.evalNode("mappers")); 这里地方也需要重点看下
mappersElement负责:
- 加载所有 Mapper 接口 和 Mapper XML 文件
- 建立 Java 接口 与 SQL 语句 的绑定关系
- 最终生成所有 SQL 操作的运行时对象 mappedStatement
java
private void mappersElement(XNode context) throws Exception {
if (context != null) {
Iterator var2 = context.getChildren().iterator();
while(true) {
while(true) {
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
// package 包扫描
if (!"package".equals(child.getName())) {
// resource 资源路径
// url 网络资源
// class 类全限定名
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
InputStream inputStream;
XMLMapperBuilder mapperParser;
if (resource == null || url != null || mapperClass != null) {
if (resource != null || url == null || mapperClass != null) {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
} else {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var12) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var10) {
var12.addSuppressed(var10);
}
}
throw var12;
}
if (inputStream != null) {
inputStream.close();
}
}
} else {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var13) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var11) {
var13.addSuppressed(var11);
}
}
throw var13;
}
if (inputStream != null) {
inputStream.close();
}
}
} else {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
}
}
return;
}
}
}
}
}
根据配置的 指定具体方法,如此处通过resource来解析Mapper.xml 文件
和XMLConfigBuilder 一样,XMLMapperBuilder会创建一个专门用于解析单个Mapper.xml文件的解析器实例
然后调用 .parse() 方法 具体解析Mapper.xml 中的各个节点
java
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
java
public void parse() {
// 检查是否已加载
if (!this.configuration.isResourceLoaded(this.resource)) {
// 阶段1:解析XML结构
this.configurationElement(this.parser.evalNode("/mapper"));
// 标记为已加载
this.configuration.addLoadedResource(this.resource);
// 阶段2:绑定接口
this.bindMapperForNamespace();
}
// 阶段3:处理延迟加载项
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
截图可见 resource 中为:资源路径 this.parser.evalNode("/mapper") 为解析的Mapper.xml中的具体内容

this.configurationElement()具体解析 Mapper.xml中的各个节点
java
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.isEmpty()) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
// 对给定命名空间的缓存配置
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
// 获得MappedStatement对象(增删改查标签)
this.sqlElement(context.evalNodes("/mapper/sql"));
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
Exception e = var3;
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + e, e);
}
}
我们可以看一下 this.cacheElement(context.evalNode("cache"));
java
private void cacheElement(XNode context) {
if (context != null) {
// 默认基础缓存实现
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);
// 默认LRU淘汰策略
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) {
Cache cache = (new CacheBuilder(this.currentNamespace)).implementation((Class)this.valueOrDefault(typeClass, PerpetualCache.class)).addDecorator((Class)this.valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();
// 将缓存添加到全局配置
this.configuration.addCache(cache);
this.currentCache = cache;
return cache;
}
// 使用建造者模式构建缓存实例
public Cache build() {
// 1. 设置默认实现
this.setDefaultImplementations();
// 2. 创建基础缓存实例
// this.implementation org.apache.ibatis.cache.impl.PerpetualCache
Cache cache = this.newBaseCacheInstance(, this.id);
// 3. 装饰基础缓存(仅对PerpetualCache)
this.setCacheProperties((Cache)cache);
if (PerpetualCache.class.equals(cache.getClass())) {
Iterator var2 = this.decorators.iterator();
while(var2.hasNext()) {
Class<? extends Cache> decorator = (Class)var2.next();
cache = this.newCacheDecoratorInstance(decorator, (Cache)cache);
this.setCacheProperties((Cache)cache);
}
cache = this.setStandardDecorators((Cache)cache);
// 4. 非PerpetualCache实现只需添加日志装饰器
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache((Cache)cache);
}
return (Cache)cache;
}
private Cache setStandardDecorators(Cache cache) {
try {
// 设置缓存大小
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (this.size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", this.size);
}
// 定时清理装饰器
if (this.clearInterval != null) {
cache = new ScheduledCache((Cache)cache);
((ScheduledCache)cache).setClearInterval(this.clearInterval);
}
// 序列化装饰器
if (this.readWrite) {
cache = new SerializedCache((Cache)cache);
}
// 输出日志
Cache cache = new LoggingCache((Cache)cache);
// 线程安全
cache = new SynchronizedCache(cache);
// 阻塞装饰器(配置blocking时)
if (this.blocking) {
cache = new BlockingCache((Cache)cache);
}
return (Cache)cache;
} catch (Exception var3) {
Exception e = var3;
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}


MyBatis缓存系统采用经典的装饰器模式,通过多层嵌套增强基础缓存功能
-
PerpetualCache
:基础缓存 -
LruCache
:LRU淘汰策略 -
SerializedCache
:对象序列化 -
LoggingCache
:日志装饰器 -
SynchronizedCache
:线程安全
再看 this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
java
private void buildStatementFromContext(List<XNode> list) {
// 检查是否配置了databaseId
if (this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
// 解析所有不带databaseId或未匹配的语句
this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
// 遍历所有SQL节点
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
// 核心解析方法
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
parseStatementNode() 方法 这是SQL语句解析的核心方法
java
public void parseStatementNode() {
// 获取语句属性(id、parameterType、resultType等)
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 解析SQL语句
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 解析其他属性
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
// 结果映射配置
String parameterMap = this.context.getStringAttribute("parameterMap");
String resultType = this.context.getStringAttribute("resultType");
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultMap = this.context.getStringAttribute("resultMap");
if (resultTypeClass == null && resultMap == null) {
resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(this.builderAssistant.getCurrentNamespace(), id);
}
String resultSetType = this.context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = this.configuration.getDefaultResultSetType();
}
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String resultSets = this.context.getStringAttribute("resultSets");
boolean dirtySelect = this.context.getBooleanAttribute("affectData", Boolean.FALSE);
// 构建MappedStatement并添加到Configuration
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
}
解析SQL语句
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
java
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 创建XML脚本解析器
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 解析脚本节点并返回SqlSource
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
// 解析动态标签(如<if>, <foreach>等)
MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
// 根据是否包含动态内容选择SqlSource实现
SqlSource sqlSource;
if (this.isDynamic) {
// 动态SQL(运行时解析)
sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
} else {
// 静态SQL(启动时解析)
sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
}
return sqlSource;
}

java
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets, boolean dirtySelect) {
if (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
id = this.applyCurrentNamespace(id, false);
MappedStatement.Builder statementBuilder = (new MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(flushCache).useCache(useCache).cache(this.currentCache).dirtySelect(dirtySelect);
ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//持有在configuration中
this.configuration.addMappedStatement(statement);
return statement;
}
}

现在Mapper.xml 中的各个节点已经解析完毕 回到 上面 this.bindMapperForNamespace();
bindMapperForNamespace()将XML映射文件与对应的Mapper接口进行绑定,是MyBatis接口映射的核心机制。
java
private void bindMapperForNamespace() {
// 获取当前命名空间(对应Mapper接口的全限定名)
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
// 尝试加载Mapper接口类
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException var4) {
}
// 如果找到接口且未注册,则添加到配置中
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);
}
}
}

java
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//接口类型(key)->工厂类
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}

第二步总结:
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
-
SqlSessionFactoryBuilder
解析 XML 配置文件 -
创建
Configuration
对象,包含所有配置信息 -
解析全局配置(settings、plugins等)
-
解析并加载所有 Mapper 文件(XML 或接口)
-
为每个 Mapper 方法创建
MappedStatement
对象 -
最终构建出
SqlSessionFactory
实例
2、运行时阶段
(3)获取SqlSession
java
// 3. 获取数据源执行器
SqlSession sqlSession = sessionFactory.openSession();
openSessionFromDataSource() 是MyBatis核心功能之一,负责创建SqlSession实例
JdbcTransaction
:基于JDBC连接的事务ManagedTransaction
:由容器管理的事务
java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
// 从全局Configuration对象获取环境配置
Environment environment = this.configuration.getEnvironment();
// 根据环境配置决定使用哪种事务工厂
// 默认为JdbcTransactionFactory,也可配置为ManagedTransactionFactory
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器 executor
Executor executor = this.configuration.newExecutor(tx, execType);
// 创建SqlSession实例
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
Exception e = var12;
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
this.configuration.newExecutor(tx, execType) 会根据type创建不同类型的 executor
java
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 如果未指定执行器类型,使用默认类型(SIMPLE)
executorType = executorType == null ? this.defaultExecutorType : executorType;
Object executor;
// 根据类型创建基础执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果启用二级缓存,用CachingExecutor包装基础执行器
if (this.cacheEnabled) {
// 装饰器模式
executor = new CachingExecutor((Executor)executor);
}
return (Executor)this.interceptorChain.pluginAll(executor);
}
执行器类型 | 特点 | 适用场景 | 性能影响 |
---|---|---|---|
SimpleExecutor | 每次执行创建新Statement | 常规操作 | 中等 |
ReuseExecutor | 重用预处理Statement对象 | 高频相同SQL | 较高 |
BatchExecutor | 批量执行更新操作 | 批量插入/更新 | 最高 |
CachingExecutor | 二级缓存装饰器 | 读多写少 | 依赖缓存命中率 |
SqlSession 创建时序图:
-
Client调用openSession():
- 客户端通过SqlSessionFactory请求一个新的会话
-
创建Transaction事务对象:
- 从环境配置中获取数据源
- 根据参数创建事务对象
- 事务隔离级别和自动提交设置在此确定
-
创建Executor执行器
- 根据ExecutorType创建不同类型的执行器
-
创建DefaultSqlSession实例
- 将配置、执行器和事务设置组合
- 返回给客户端可用的SqlSession

第三步总结:
SqlSession sqlSession = sessionFactory.openSession();
-
通过
SqlSessionFactory.openSession()
创建 -
内部创建
Executor
实例(决定是否启用缓存、批处理等) -
创建
Transaction
事务对象 -
关键功能:
- 提供CRUD操作API
- 管理一级缓存
- 事务边界控制
(4)获取Mapper代理对象
java
// 4. 获取Mapper代理对象
UserMapper inventoryMapper = sqlSession.getMapper(UserMapper.class);
java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从knownMappers注册表中查找指定接口类型的代理工厂
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 通过工厂方法创建接口代理
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
Exception e = var5;
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// mapperInterface 目标Mapper接口类 通过JDK动态代理返回代理对象
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
第四步总结:
UserMapper inventoryMapper = sqlSession.getMapper(UserMapper.class);
- 通过
sqlSession.getMapper()
获取 knownMappers.get(type)
查找指定接口类型的代理工厂- 使用 JDK 动态代理创建
MapperProxy
实例 - 代理对象会将所有方法调用转发给
MapperProxy
(5) 调用mapper的方法
java
// 5.调用mapper的方法
User user = inventoryMapper.getUserById(2);
所有的 Mapper 都是 MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法
- MapperProxy拦截阶段
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 检查是否为Object类声明的方法
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
Throwable t = var5;
throw ExceptionUtil.unwrapThrowable(t);
}
}
// method.invoke(this, args) 不代理这些基础方法,直接执行
// this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); 通过cachedInvoker获取方法对应的调用处理器 将方法调用委托给MapperMethodInvoke
- MapperMethod执行阶段
MapperMethod.execute()
核心逻辑:
java
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case SELECT:
if (method.returnsVoid()) {
executeWithResultHandler(sqlSession, args);
return null;
} else if (method.returnsMany()) {
return executeForMany(sqlSession, args);
} else {
return executeForOne(sqlSession, args);
}
case INSERT:
// 插入操作处理...
case UPDATE:
// 更新操作处理...
case DELETE:
// 删除操作处理...
}
}
- SqlSession处理阶段
以selectOne
为例:selectOne查询一个和查询多个其实是一样的。
java
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
// 获取保存在configuration中的MappedStatement
MappedStatement ms = this.configuration.getMappedStatement(statement);
this.dirty |= ms.isDirtySelect();
// 实际委托给执行器
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
Exception e = var10;
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
return var6;
}

- Executor执行阶段
BaseExecutor.query()
方法:
java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql(已解析的SQL与参数映射)
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 查询数据库(含二级缓存逻辑)
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
- Statement创建与执行
从query方法会走到doQuery方法中
SimpleExecutor.doQuery()
:
java
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
// 准备Statement
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 执行查询
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
- 结果集映射阶段
DefaultResultSetHandler.handleResultSets()
:
java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
List<Object> multipleResults = new ArrayList();
int resultSetCount = 0;
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
// 获取配置的ResultMap集合
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
this.validateResultMapsCount(rsw, resultMapCount);
// 遍历处理每个结果集
while(rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
// 调用handleResultSet进行实际映射
this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
String[] resultSets = this.mappedStatement.getResultSets();
if (resultSets != null) {
while(rsw != null && resultSetCount < resultSets.length) {
// 处理嵌套映射
ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
}
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
}
return this.collapseSingleResultList(multipleResults);
}

调用mapper的时序图:

第五步总结:
User user = inventoryMapper.getUserById(2);
-
代理拦截 :
MapperProxy.invoke()
拦截方法调用 -
方法转换 :将方法转为
MapperMethod
对象 -
参数处理 :
ParamNameResolver
处理参数 -
SQL 执行:
- 通过
Executor
执行查询 StatementHandler
创建 Statement 对象ParameterHandler
设置参数ResultSetHandler
处理结果集映射
- 通过
-
结果返回:根据返回类型(单对象、List、Map等)返回适当结果
五、总结MyBatis工作流程时序图

总结:
-
初始化阶段:
- 解析全局配置文件(数据源、插件等)
- 加载所有Mapper.xml/注解配置
- 构建
Configuration
单例
-
会话创建阶段:
- 根据配置创建事务管理器
- 初始化执行器(Simple/Reuse/Batch)
- 组装成
SqlSession
-
Mapper调用阶段:
- 动态代理拦截接口方法
- 转换为
MapperMethod
执行 - 路由到对应SqlSession操作
-
SQL 执行阶段:
- 参数处理:
#{param}
→ PreparedStatement参数 - 结果映射:ResultSet → Java对象
- 二级缓存处理
- 参数处理:
-
关闭阶段:
-
事务提交/回滚
-
连接归还连接池
-
清理线程本地资源
-