MyBatis源码学习总结

一、MyBatis 的产生背景

MyBatis 主要为了解决传统 JDBC 编程中的以下痛点:

  1. 繁琐的样板代码: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;
    }
}
  1. SQL 与 Java 代码混杂:业务逻辑中硬编码 SQL 语句,难以维护
  2. 结果集映射复杂:手动将 ResultSet 转换为 Java 对象繁琐易错
  3. 缺乏缓存机制:需要手动实现查询结果缓存

二、MyBatis 的核心优点

  1. SQL 与代码分离:SQL 写在 XML 中,与 Java 代码解耦
  2. 自动对象映射:自动将结果集映射到 POJO,支持复杂映射
  3. 动态 SQL:提供强大的动态 SQL 功能,避免拼接 SQL 字符串
  4. 插件机制:可通过插件扩展功能(如分页、性能监控)
  5. 轻量级:不依赖容器,配置简单,学习曲线平缓

三、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负责:

  1. 加载所有 Mapper 接口Mapper XML 文件
  2. 建立 Java 接口SQL 语句 的绑定关系
  3. 最终生成所有 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);

  1. SqlSessionFactoryBuilder 解析 XML 配置文件

  2. 创建 Configuration 对象,包含所有配置信息

  3. 解析全局配置(settings、plugins等)

  4. 解析并加载所有 Mapper 文件(XML 或接口)

  5. 为每个 Mapper 方法创建 MappedStatement 对象

  6. 最终构建出 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 创建时序图:

  1. Client调用openSession():

    1. 客户端通过SqlSessionFactory请求一个新的会话
  2. 创建Transaction事务对象:

    1. 从环境配置中获取数据源
    2. 根据参数创建事务对象
    3. 事务隔离级别和自动提交设置在此确定
  3. 创建Executor执行器

    1. 根据ExecutorType创建不同类型的执行器
  4. 创建DefaultSqlSession实例

    1. 将配置、执行器和事务设置组合
    2. 返回给客户端可用的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()方法

  1. 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
  1. 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:
            // 删除操作处理...
    }
}
  1. 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;
}
  1. 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);
}
  1. 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;
}
  1. 结果集映射阶段

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

  1. 代理拦截MapperProxy.invoke() 拦截方法调用

  2. 方法转换 :将方法转为 MapperMethod 对象

  3. 参数处理ParamNameResolver 处理参数

  4. SQL 执行

    1. 通过 Executor 执行查询
    2. StatementHandler 创建 Statement 对象
    3. ParameterHandler 设置参数
    4. ResultSetHandler 处理结果集映射
  5. 结果返回:根据返回类型(单对象、List、Map等)返回适当结果


五、总结MyBatis工作流程时序图

总结:

  1. 初始化阶段

    1. 解析全局配置文件(数据源、插件等)
    2. 加载所有Mapper.xml/注解配置
    3. 构建Configuration单例
  2. 会话创建阶段

    1. 根据配置创建事务管理器
    2. 初始化执行器(Simple/Reuse/Batch)
    3. 组装成SqlSession
  3. Mapper调用阶段

    1. 动态代理拦截接口方法
    2. 转换为MapperMethod执行
    3. 路由到对应SqlSession操作
  4. SQL 执行阶段

    1. 参数处理:#{param} → PreparedStatement参数
    2. 结果映射:ResultSet → Java对象
    3. 二级缓存处理
  5. 关闭阶段

    1. 事务提交/回滚

    2. 连接归还连接池

    3. 清理线程本地资源

相关推荐
.生产的驴10 分钟前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
景天科技苑19 分钟前
【Rust】Rust中的枚举与模式匹配,原理解析与应用实战
开发语言·后端·rust·match·enum·枚举与模式匹配·rust枚举与模式匹配
凯酱37 分钟前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis
追逐时光者1 小时前
MongoDB从入门到实战之Docker快速安装MongoDB
后端·mongodb
方圆想当图灵1 小时前
深入理解 AOP:使用 AspectJ 实现对 Maven 依赖中 Jar 包类的织入
后端·maven
豌豆花下猫1 小时前
Python 潮流周刊#99:如何在生产环境中运行 Python?(摘要)
后端·python·ai
嘻嘻嘻嘻嘻嘻ys1 小时前
《Spring Boot 3 + Java 17:响应式云原生架构深度实践与范式革新》
前端·后端
异常君1 小时前
线程池隐患解析:为何阿里巴巴拒绝 Executors
java·后端·代码规范
mazhimazhi1 小时前
GC垃圾收集时,居然还有用户线程在奔跑
后端·面试
Python私教1 小时前
基于 Requests 与 Ollama 的本地大模型交互全栈实践指南
后端