MyBatis 架构与源码实现串讲

最近我完成了对 MyBatis 源码的学习,同时也模仿 MyBatis 自行实现了一个 ORM 框架。在此背景下,我打算在不查阅资料的情况下,对 MyBatis 的基本架构进行回顾。

1 MyBatis 架构设计

MyBatis 的功能架构分为以下三层:

  • 接口层:提供给外部使用的接口 API,开发人员可以通过这些 API 来操作数据库,接口再通过数据处理层完成具体的数据处理。
  • 数据处理层:负责具体的 SQL 查找、解析和执行结果映射等操作。
  • 基础支持层:负责最基础的功能,例如连接管理、事务管理、配置加载和缓存处理等。

架构图如下:

MyBatis 架构设计

2 MyBatis 各组件回顾

组件说明
SqlSessionMyBatis 的顶层 API,表示和数据库交互的会话(会话模式),提供了对数据库的增删改查接口
ExecutorMyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护
StatementHandler封装了 JDBC Statement 操作,真正负责对 JDBC API 的调用,例如设置参数、执行调用、转换 Statement 结果集等
ParameterHandler将用户传递的参数转换为 JDBC Statement 需要的参数
ResultSetHandler将 JDBC Statement 返回的结果集转换成 List 集合
TypeHandler负责 Java 数据类型和 JDBC 数据类型之间的转换
MappedStatement每个 MappedStatement 都会维护一条 (select|update|delete|insert) 节点的封装
SqlSource负责根据用户传递的 ParameterObject,动态的生成 SQL 语句,将信息封装到 BoundSql 对象,并返回
BoundSql表示动态生成的 SQL 语句以及相应的参数信息

他们之间的层次结构关系如下:

MyBatis 层次结构

3 MyBatis 核心源码剖析

这是一个通过 MyBatis API 查询数据库的示例代码:

1
2
3
4
5
6
7
// 1. 获取 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 通过工厂创建默认 SqlSession 实现
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
  // 3. 通过会话定义的查询接口,完成查询
  List<User> list = sqlSession.selectList("io.maling.mapper.UserMapper.getUserByName")
}

下面我们从该 API 的使用示例着手,分析 MyBatis 的源码实现。

3.1 SqlSessionFactory 工厂的创建

首先看创建 SqlSession 的工厂类 SqlSessionFactory。这个工厂是通过构建者模式创建的,其核心重载实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
  try {
    // 解析 MyBatis 核心配置文件,跟 mapper 映射文件
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
    // 会根据得到的 Configuration 全局配置对象
    //  创建一个 DefaultSqlSessionFactory 对象
    return build(parser.parse());
  } catch finally ...
}

// 通过配置创建默认工厂实现
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

MyBatis 在初始化的时候,会将配置文件信息全部加载到内存中,通过 org.apache.ibatis.session.Configuration 实例维护。最终通过该配置实例完成默认工厂对象的创建。

3.1.1 Configuration 解析配置

接下来我们分析 Configuration 的构建过程,也就是配置文件的解析过程。XMLConfigBuilder 会在其 parse 方法中完成对配置文件的解析:

1
2
3
4
5
6
7
8
9
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 用 XPATH 解析配置文件
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

解析后的数据结构与 XML 配置文件完全一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void parseConfiguration(XNode root) {
  try {
    // 解析 <properties> 标签
    propertiesElement(root.evalNode("properties"));
    // 解析 <settings> 标签
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    // 处理日志相关组件
    loadCustomLogImpl(settings);
    // 解析 <typeAliases> 标签
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析 <plugins> 标签
    pluginElement(root.evalNode("plugins"));
    // 解析 <objectFactory> 标签
    objectFactoryElement(root.evalNode("objectFactory"));
    // 解析 <objectWrapperFactory> 标签
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 解析 <reflectorFactory> 标签
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // 设置 <settings> 标签的元素
    settingsElement(settings);
    // 解析 <environments> 标签
    environmentsElement(root.evalNode("environments"));
    // 解析 <databaseIdProvider> 标签
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析 <typeHandlers> 标签
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析 <mappers> 标签,里边会封装 MappedStatement
    mapperElement(root.evalNode("mappers"));
  } catch ...
}

这里重点关注 mapperElement(root.evalNode("mappers")); 这行代码,这里会完成 Mapper 映射配置文件的解析,并将每条 SQL 语句以 MappedStatement 的形式存入 Configuration.mappedStatements Map 中,每个 MappedStatement 对应的 Key 为 namespace + id。

创建完 Configuration 后,紧接着在 build() 方法中调用 DefaultSqlSessionFactory 的构造方法完成默认会话工厂的创建。

3.1.2 获取 SqlSession 实例

调用 DefaultSqlSessionFactory 中的 openSession() 方法,可以创建默认的 DefaultSqlSession 实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // 获取 Environment 对象
    final Environment environment = configuration.getEnvironment();
    // 获取 TransactionFactory 对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 从数据源中创建 Transaction
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 根据配置创建 Executor 对象
    final Executor executor = configuration.newExecutor(tx, execType);
    // 在 Executor 的基础上创建 DefaultSqlSession 对象
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch finally ...
}

DefaultSqlSession 内部封装了配置类和 Executor 执行器等信息。

3.2 调用 SqlSession 接口方法

拿到 DefaultSqlSession 实例,我们就可以调用 SqlSession 接口中定义的方法完成数据库操作了。对于默认的会话实现,其指令是委托给 Executor 执行器完成的,以上文 Demo 的 selectList 为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    // 1. 从配置类中拿到 MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    dirty |= ms.isDirtySelect();
    // 2. 通过执行器查询
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

SqlSession 接口首先从 Configuration 中根据 statement(也就是 namespace + id)拿到了待执行 Sql 的 MappedStatement。而后将该 MappedStatement 与 parameter 等参数一同传递给 Executor 执行器处理。

3.2.1 MappedStatement Binding 模块

MappedStatement 实例的作用就是描述一个 SQL 语句。每个 MappedStatement 实例都会对应 Mapper 映射配置文件中的一个 CRUD 节点,Mapper 配置中的 selectupdate 等 SQL 标签最终都被封装成了一个一个的 MappedStatement

3.3 Execurot 维护一级缓存

上文讲过,SqlSession 执行 SQL 是交给 Executor 的,但是 Executor 也并没有直接操作 JDBC,而是主要完成了对一级缓存的处理。以 query() 方法为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    // 非嵌套查询,并且<select>标签配置的 flushCache 属性为 true 时,才会清空一级缓存
    // 注意:flushCache 配置项会影响一级缓存中结果对象存活时长
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;// 增加查询层数
    // 查询一级缓存
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 对存储过程出参的处理:如果命中一级缓存,则获取缓存中保存的输出参数,
      // 然后记录到用户传入的实参对象中
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // queryFromDatabase() 方法内部首先会在 localCache 中设置一个占位符,
      // 然后调用 doQuery() 方法完成数据库查询,并得到映射后的结果对象,
      // doQuery() 方法是一个抽象方法,由 BaseExecutor 的子类具体实现
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    // 当前查询完成,查询层数减少
    queryStack--;
  }
  if (queryStack == 0) { // 完成嵌套查询的填充
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // 清空 deferredLoads 集合
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // 根据配置决定是否清空 localCache
      clearLocalCache();
    }
  }
  return list;
}

3.3.1 Exector 创建 StatementHandler

顺着 queryFromDatabase 往下找,最终执行的是 doQuery() 方法。我们以 SimpleExecutor 这个执行器实现为例进行介绍:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// SimpleExecutor
@Override
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 对象,实际返回的是 RoutingStatementHandler 对象
    // 其中根据 MappedStatement.statementType 选择具体的 StatementHandler 实现
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // 完成 StatementHandler 的创建和初始化,该方法会调用 StatementHandler.prepare() 方法创建
    // Statement 对象,然后调用 StatementHandler.parameterize() 方法处理占位符
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 调用 StatementHandler.query() 方法,执行 SQL 语句,并通过 ResultSetHandler 完成结果集的映射
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

最终执行器利用装饰器模式创建了用于真正执行 JDBC 代码的 StatementHandler

3.4 StatementHandler

org.apache.ibatis.executor.statement.StatementHandler 接口是 MyBatis 操作 JDBC 的核心,它主要完成以下两类工作:

  • 替换将 JDBC 的 PreparedStatement? 占位符替换为实参;
  • 通过 query 方法调用 JDBC execute 方法,并通过 ResultSetHandler 将返回的结果集封装为 Java List。

3.4.1 StatementHandler 创建 Statement

创建完成 StatementHandler 后(默认是 PreparedStatementHandler),最终会调用 prepareStatement() 完成对 JDBC 代码的调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // 获取 JDBC Connection 连接
  Connection connection = getConnection(statementLog);
  // prepare 完成执行
  stmt = handler.prepare(connection, transaction.getTimeout());
  // 为 SQL 语句绑定实参
  handler.parameterize(stmt);
  return stmt;
}
  • 该方法首先在 getConnection 中通过层层调用,拿到数据库连接。
  • 然后调用 PreparedStatementHandlerprepare() 方法完成 JDBC java.sql.PreparedStatement 实例的创建。
  • 最后调用 PreparedStatementHandlerparameterize() 方法为 SQL 语句绑定实参(涉及到 ParameterHandler)。

3.4.2 StatementHandler parameterize 绑定实参

PreparedStatement 中的 SQL 占位符是 ?,所以需要在这里完成实参替换:

1
2
3
4
@Override
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}

里边涉及到 parameterHandler 的调用,这里以默认转换器 DefaultParameterHandler 为例进行介绍:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
public void setParameters(PreparedStatement ps) {
    // 拿到 ParameterMap
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                // 获取实参值
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                // 每⼀个 Mapping 都有⼀个 TypeHandler,
                //  根据 TypeHandler 来对 preparedStatement 进行参数设置
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                try {
                    // 底层会调用 PreparedStatement.set*() 方法完成实参替换
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch ...
            }
        }
    }
}

该方法首先从自己关联的 boundSql 中获取关联的所有 ParameterMapping,然后便利所有 ParameterMapping,利用它的 TypeHandler 将 Java 数据类型转换为 JdbcType,并完成参数的设置。

3.4.3 StatementHandler query 调用

完成上述设置后,最终 Executor 会调用 StatementHandlerquery() 方法完成实际的查询请求:

1
2
3
4
5
6
7
8
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 调⽤ preparedStatemnt.execute() ⽅法,然后将 resultSet 交给 ResultSetHandler 处理
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    // 使⽤ ResultHandler 来处理 ResultSet
    return resultSetHandler.handleResultSets(ps);
}

3.4.4 ResultSetHandler 解析结果集

resultSetHandler.handleResultSets() 方法会将 Statement 语句执行后生成的结果集换转为 Java List:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    // 用于记录每个 ResultSet 映射出来的 Java 对象
    final List<Object> multipleResults = new ArrayList<>();
    int resultSetCount = 0;
    // 从 Statement 中获取第一个 ResultSet,其中对不同的数据库有兼容处理逻辑,
    // 这里拿到的 ResultSet 会被封装成 ResultSetWrapper 对象返回
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取这条 SQL 语句关联的全部 ResultMap 规则。如果一条 SQL 语句能够产生多个 ResultSet,
    // 那么在编写 Mapper.xml 映射文件的时候,我们可以在 SQL 标签的 resultMap 属性中配置多个
    // <resultMap>标签的 id,它们之间通过","分隔,实现对多个结果集的映射
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 验证结果映射计数
    validateResultMapsCount(rsw, resultMapCount);
    // 遍历 ResultMap 集合
    while (rsw != null && resultMapCount > resultSetCount) {
      // 根据 ResultMap 中定义的映射规则处理 ResultSet,并将映射得到的 Java 对象添加到
      // multipleResults 集合中保存
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 获取下一个 ResultSet
      rsw = getNextResultSet(stmt);
      // 清理 nestedResultObjects 集合,这个集合是用来存储中间数据的
      cleanUpAfterHandlingResultSet();
      // 递增 ResultSet 编号
      resultSetCount++;
    }
    // 根据 ResultSet 的名称处理嵌套映射
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        // 获取 nextResultMaps 中的 ResultMapping 对象
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          // 获取 ResultMapping 中指定的 ResultMap 映射规则
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          // 进行结果集映射,得到的结果对象会添加到外层结果对象的相应属性中
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        // 继续获取下一个 ResultSet
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    // 返回全部映射得到的 Java 对象
    return collapseSingleResultList(multipleResults);
}


欢迎关注我的公众号,第一时间获取文章更新:

微信公众号

相关内容