MyBatis源码学习总结

179 阅读15分钟

一、MyBatis 的产生背景

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

  1. 繁琐的样板代码:JDBC 需要大量重复代码(获取连接、创建语句、处理结果集等)
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
ExecutorSQL 执行器,负责 SQL 的生成和缓存维护
MappedStatement封装 SQL 语句、输入输出参数等信息
Configuration所有配置信息的容器,MyBatis 的核心
MapperProxy动态代理实现,将接口方法调用转为数据库操作

四、MyBatis 运行流程分析

代码结构:

实体类:

@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接口:

public interface UserMapper {
    List<User> getUser();
    User getUserById(@Param("id") Integer id);
}

Mapper.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:

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

测试类

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)读取配置文件

// 1. 读取配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
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

// 2. 解析所有XML配置文件(setting、plugin、Mapper映射文件、)
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder().build(reader) 构造者模式

将封装好的reader读取器 构建成 SqlSessionFactory,解析配置文件

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() 方法 中做了哪些事?

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

// 解析各个节点
// 里面的各个方法都会把解析好的属性设置到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 整个运行周期,启动时解析并缓存所有配置,运行时直接使用,避免每次操作都重新读取配置文件

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

image.png this.mappersElement(root.evalNode("mappers")); 这里地方也需要重点看下

mappersElement负责:

  1. 加载所有 Mapper 接口Mapper XML 文件
  2. 建立 Java 接口SQL 语句 的绑定关系
  3. 最终生成所有 SQL 操作的运行时对象 mappedStatement
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 中的各个节点

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;
}
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中的各个节点

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

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

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语句解析的核心方法

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

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

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接口映射的核心机制。

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

}

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

// 3. 获取数据源执行器
SqlSession sqlSession = sessionFactory.openSession();

openSessionFromDataSource() 是MyBatis核心功能之一,负责创建SqlSession实例

  • JdbcTransaction:基于JDBC连接的事务
  • ManagedTransaction:由容器管理的事务
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

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代理对象

// 4. 获取Mapper代理对象
UserMapper inventoryMapper = sqlSession.getMapper(UserMapper.class);
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的方法

// 5.调用mapper的方法
User user = inventoryMapper.getUserById(2);

所有的 Mapper 都是 MapperProxy 代理对象,所以任意的方法都是执行MapperProxy 的invoke()方法

  1. MapperProxy拦截阶段
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()核心逻辑:

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查询一个和查询多个其实是一样的。

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()方法:

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

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

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. 清理线程本地资源