MyBatis框架源码解析

1. MyBatis工作原理解析

1.1 全局配置解析与SqlSessionFactory构建

MyBatis的启动流程始于对全局配置文件(mybatis-config.xml)的解析。该配置文件定义了数据库连接信息、事务管理器、类型别名、映射器(Mapper)等核心参数。MyBatis通过建造者模式(Builder Pattern)完成这一过程,核心类包括 SqlSessionFactoryBuilderXMLConfigBuilder

源码实现:SqlSessionFactoryBuilder.build()

// 示例:加载配置文件并创建SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

在底层源码中,SqlSessionFactoryBuilder.build() 方法通过 XMLConfigBuilder 解析 XML 配置文件,并将其封装为 Configuration 对象:

public SqlSessionFactory build(InputStream inputStream) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
        return new DefaultSqlSessionFactory(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }
}
  • XMLConfigBuilder:负责解析 mybatis-config.xml 中的各个标签(如 <properties><settings><typeAliases><mappers>),并将解析结果填充到 Configuration 对象中。
  • Configuration:全局配置的核心容器,存储了数据库连接信息、类型别名映射、Mapper注册信息等。

配置文件示例

<!-- mybatis-config.xml -->
<configuration>
    <properties resource="db.properties"/>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.example.model"/>
    </typeAliases>
    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

1.2 SqlSession的创建

SqlSessionFactory 是 MyBatis 的核心工厂接口,其 openSession() 方法通过工厂模式(Factory Pattern)创建 SqlSession 实例。SqlSession 代表一次数据库会话,用于执行 SQL 操作、管理事务等。

源码实现:SqlSessionFactory.openSession()

// 示例:通过SqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

底层实现中,DefaultSqlSessionFactory 默认实现了 SqlSessionFactory,openSession() 方法返回的是 DefaultSqlSession 实例:

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    TransactionFactory transactionFactory = configuration.getTransactionFactory();
    Transaction transaction = transactionFactory.newTransaction(dataSource, level, autoCommit);
    Executor executor = configuration.newExecutor(transaction, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
}

1.3 Mapper接口代理的动态生成

MyBatis 的 Mapper 接口代理生成是其核心特性之一,通过 JDK 动态代理 将接口方法与 SQL 映射文件绑定。其底层逻辑依赖于 MapperProxyFactoryMapperProxy 类,结合 Configuration 中的 MappedStatement 实现方法调用的动态绑定。

1.3.1 获取 Mapper 实例

当我们调用 sqlSession.getMapper(UserMapper.class) 方法时,MyBatis 会尝试获取一个 UserMapper 的实例。这一步骤由 DefaultSqlSession 类中的 getMapper() 方法触发:

public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

此方法内部委托给 Configuration 对象去获取具体的 Mapper 实例。

1.3.2 MapperRegistry 查找或创建 MapperProxyFactory

Configuration 类中的 getMapper() 方法进一步调用了 MapperRegistry 来查找或创建对应的 MapperProxyFactory

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry.getMapper() 方法中,MyBatis 会检查是否已存在该类型(type)的 MapperProxyFactory,如果不存在,则抛出异常提示未注册的类型错误。如果找到了相应的工厂,则使用它来创建 Mapper 接口的代理对象。

1.3.3 动态生成 MapperProxy

一旦确定了 MapperProxyFactory,就会调用其 newInstance(sqlSession) 方法来创建 Mapper 接口的代理对象。这个过程中,MapperProxyFactory 使用 JDK 动态代理机制生成一个实现了指定 Mapper 接口的代理对象:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(
        mapperInterface.getClassLoader(),
        new Class[] { mapperInterface },
        mapperProxy
    );
}

这里的 mapperProxy 是一个 MapperProxy 实例,它实现了 InvocationHandler 接口,负责拦截所有对 Mapper 接口方法的调用。

1.3.4 调用 Mapper 方法时的行为

当用户调用 Mapper 接口中的某个方法时(例如 userMapper.selectById(1)),JDK 动态代理机制会将该调用转发到 MapperProxy.invoke() 方法。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
}

以下是关键流程的详细分析:

(1) cachedInvoker() 方法的缓存机制

cachedInvoker() 方法用于从缓存中获取或创建 MapperMethodInvoker,避免重复解析相同的方法:

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
        return invoker;
    }
    return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
            // 处理 Java 8+ 的 default 方法
            return new DefaultMethodInvoker(...);
        } else {
            // 普通方法:创建 PlainMethodInvoker
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
    });
}
  • methodCache 是一个 ConcurrentHashMap<Method, MapperMethodInvoker>,用于缓存已解析的方法调用器。
  • 如果是普通方法(非 default),则创建 PlainMethodInvoker,其内部封装了一个 MapperMethod 实例。
(2) MapperMethod 的 SQL 映射解析

MapperMethod 的构造函数会通过 sqlSession.getConfiguration() 获取 MyBatis 的配置信息,并解析当前方法对应的 MappedStatement(即 XML 或注解中定义的 SQL 映射):

public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {
    this.command = new SqlCommand(configuration, mapperInterface, method);
    this.method = method;
}

SqlCommand 解析 MappedStatement 的关键逻辑
SqlCommandMapperMethod 的内部类,负责解析方法与 SQL 的映射关系。以下是其核心逻辑:

public static class SqlCommand {
    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
        final String methodName = method.getName();
        final Class<?> declaringClass = method.getDeclaringClass();
        
        // 1. 尝试通过接口全名 + 方法名查找 MappedStatement
        MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
        
        if (ms == null) {
            // 2. 如果没有找到,则检查是否是 @Flush 注解方法
            if (method.getAnnotation(Flush.class) != null) {
                name = null;
                type = SqlCommandType.FLUSH;
            } else {
                throw new BindingException("Invalid bound statement (not found): " 
                    + mapperInterface.getName() + "." + methodName);
            }
        } else {
            // 3. 成功获取 MappedStatement
            name = ms.getId(); // 即接口全名 + 方法名
            type = ms.getSqlCommandType(); // SELECT/INSERT/UPDATE/DELETE 等
            if (type == SqlCommandType.UNKNOWN) {
                throw new BindingException("Unknown execution method for: " + name);
            }
        }
    }

    // 递归查找接口及父接口中的 MappedStatement
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
        String statementId = mapperInterface.getName() + "." + methodName;
        if (configuration.hasStatement(statementId)) {
            return configuration.getMappedStatement(statementId);
        } else if (mapperInterface.equals(declaringClass)) {
            return null;
        }
        // 递归检查父接口
        for (Class<?> superInterface : mapperInterface.getInterfaces()) {
            if (declaringClass.isAssignableFrom(superInterface)) {
                MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                if (ms != null) {
                    return ms;
                }
            }
        }
        return null;
    }
}
  • resolveMappedStatement() 的作用:
    1. 构造 statementId接口全名 + "." + 方法名(如 com.example.UserMapper.selectById)。
    2. Configuration 中查找对应的 MappedStatement
    3. 如果当前接口未找到,递归检查其父接口(支持接口继承)。
    4. 如果最终未找到 MappedStatement,抛出 BindingException 异常(提示未绑定 SQL)。
(3) MapperMethod.execute() 执行 SQL

一旦 MapperMethod 成功解析出 MappedStatement,调用 execute() 方法时会根据 SQL 类型(SELECT/INSERT 等)调用 SqlSession 的对应方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    switch (command.getType()) {
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                return null;
            } else if (method.returnsMany()) {
                return executeForMany(sqlSession, args);
            } else {
                return sqlSession.selectOne(command.getName(), param); // command.getName() 即 MappedStatement.id
            }
        // 其他 SQL 类型处理...
    }
}
  • 关键点
    • command.getName() 返回的是 MappedStatement.id,即 接口全名 + 方法名
    • SqlSession 根据这个 id 找到具体的 SQL 映射,并通过 Executor 执行。

1.3.5 关键类及其职责总结

类名 主要职责
SqlSession 提供与数据库交互的主要 API,包括获取 Mapper 实例等。
Configuration 管理 MyBatis 配置信息,包括 Mapper 注册表等。
MapperRegistry 注册并管理所有 Mapper 接口及其对应的 MapperProxyFactory
MapperProxyFactory 工厂类,负责创建 Mapper 接口的代理对象。
MapperProxy JDK 动态代理的 InvocationHandler 实现,拦截接口方法调用。
MapperMethodInvoker 定义如何调用 Mapper 方法,有两种实现:PlainMethodInvokerDefaultMethodInvoker
MapperMethod 封装 Mapper 接口方法与 SQL 映射之间的关系,处理参数解析、SQL 执行及结果映射。

1.4 SQL执行与结果映射

1.4.1 MappedStatement 的构建

MappedStatement 是 SQL 语句和映射关系的容器,其构建由 XMLMapperBuilder 完成:

public class XMLMapperBuilder extends BaseBuilder {
    private void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            bindMapperForNamespace();
        }
        ...
    }

    private void configurationElement(XNode context) {
        String namespace = context.getStringAttribute("namespace");
        try {
            this.namespace = namespace;
            cacheRefElement(context.evalNode("cache-ref"));
            cacheElement(context.evalNode("cache"));
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            sqlElement(context.evalNodes("/mapper/sql"));
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}
  • namespace:XML 文件的 namespace 属性必须与 Mapper 接口全类名一致。
  • MappedStatement 的注册
    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }
    

1.4.2 SQL 执行的底层流程

SQL 执行的核心在 Executor,其 query() 方法调用链如下:

// DefaultSqlSession
public <E> List<E> selectList(String statement, Object parameter) {
    return executor.query(mappedStatement, wrapCollection(parameter));
}

// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    ...
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

// BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    ...
  }

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    ...
  }

// SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

// PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 最终调用JDBC的接口
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

1.4.3 结果映射的实现

结果映射的核心逻辑在 ResultSetHandler,其 handleResultSets() 方法将 ResultSet 映射为 Java 对象:

public class DefaultResultSetHandler {
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ResultSetWrapper rsw = getFirstResultSet(stmt);
        List<Object> multipleResults = new ArrayList<>();
        processResultSet(rsw, resultMaps, multipleResults, null);
        return collapseSingleResultList(multipleResults);
    }

    private void processResultSet(ResultSetWrapper rsw, List<ResultMap> resultMaps, List<Object> multipleResults, String columnPrefix) {
        ResultMap resultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), rsw.getColumnNames());
        DefaultResultHandler resultHandler = new DefaultResultHandler();
        handleResultSet(rsw, resultMap, resultHandler, columnPrefix);
        multipleResults.add(resultHandler.getResultObject());
    }
}
  • ResultMap 的自动创建
    • 如果未显式定义 resultMap,MyBatis 会根据 resultType 自动生成隐式 ResultMap
  • 字段与属性的匹配
    • 默认使用字段名与 JavaBean 属性名匹配(可配置 mapUnderscoreToCamelCase 开启下划线转驼峰)。
    • 自定义映射通过 <resultMap> 指定字段与属性的对应关系。

1.5总结

MyBatis 的 Mapper 接口代理通过 JDK 动态代理实现,其核心在于 MapperProxy 对方法调用的拦截和 MappedStatement 的绑定。SQL 执行依赖 ExecutorStatementHandler 的协作,结果映射则通过 ResultSetHandler 将数据库结果集转换为 Java 对象。这一流程贯穿了 MyBatis 的核心设计模式(如代理、策略模式)和关键数据结构(如 ConfigurationMappedStatement)。

posted @ 2025-05-14 00:03  cmk33  阅读(193)  评论(0)    收藏  举报