返回

技术干货:从头到尾剖析 MyBatis 的 SQL 语句创建过程,让你轻松掌握 ORM 精髓

后端

前言

前些日子,阿昌写过一篇【MyBatis 的 SqlSessionFacotry 的创建过程】的菜鸡文章,这里我打算再记录一篇,关于 MyBatis 的 SQL 语句的创建过程。

前戏

同样,学习 MyBatis 就必须要从【SQL】入手,因为 MyBatis 作为一套知名的 ORM 框架,它最核 Heartbeat的便是对 SQL 语句的处理。搞懂 MyBatis 是如何处理 SQL 语句的,是学习 MyBatis 的必备功力。

接下来我们就看看 MyBatis 是如何处理 SQL 语句的吧!

MyBatis 创建 SQL 语句的执行过程

MyBatis 在创建 SQL 语句时,是分为很多步,我们先来看一下执行的顺序:

  • 解析 SQL 片段 :根据 Mapper 中配置的 result,进行占位符的解析。
  • 组建参数映射 :将参数映射到 PreparedStatement 中。
  • 组建 MappedStatement :将 SQL 语句和参数映射封装到一个对象中。
  • 执行 SQL 语句 :通过 JDBC 的 Statement 执行 SQL 语句并返回结果。

1. 解析 SQL 片段

我们先来看一下第一个过程:

/**
 * 将 Mapper 中配置的 Result 中的参数占位符,解析到一个 map 中
 * 最终的 key 就是这个占位符, value 就是占位符所对应的字段名
 */
private Map<String, String> parseSqlFragment(Node node) {
  Map<String, String> sqlFragments = new HashMap<>();
  if (node == null) {
    return sqlFragments;
  }
  int nodeType = node.getNodeType();
  String nodeName = node.getName();
  if (nodeType == SqlMapperNode.SQL_MAPPER_NODE_TYPE) {
    Node[] children = node.getChildren();
    for (Node child : children) {
      sqlFragments = parseSqlFragment(child);
    }
  } else if (nodeName.equals(SqlMapperNode.RESULT_NODE_NAME)) {
    String property = node.getAttributes()[0].getValue();
    String column = node.getAttributes()[1].getValue();
    sqlFragments.put(column, property);
  }
  return sqlFragments;
}

2. 组建参数映射

我们继续来看一下第二个过程:

/**
 * 将参数映射到 PreparedStatement 中
 */
private void buildParameterMappings(String sql) {
  parameterMap = new HashMap<>();

  String[] sqlFragments = sql.split(SqlMapperNode.SQL_FRAGMENT_SEPARATOR);
  for (String sqlFragment : sqlFragments) {
    String parameterMapString = sqlFragment.substring(SqlMapperNode.SQL_FRAGMENT_PARAMETER_START, SqlMapperNode.SQL_FRAGMENT_PARAMETER_END);
    if (parameterMapString != null) {
      ParameterMap parameterMap = newParameterMap(parameterMapString);
      parameterMap.put(parameterMap.getId(), parameterMap);
    }
  }
}

3. 组建 MappedStatement

接下来我们继续看第三个过程:

/**
 * 将 SQL 语句和参数映射封装到一个对象中
 */
private void buildMappedStatement() {
  mappedStatement = new MappedStatement();
  // 这里省略了大量的 setXXX 操作,可以参考源码

}

4. 执行 SQL 语句

最后我们来看最后一个过程:

/**
 * 通过 JDBC 的 Statement 执行 SQL 语句并返回结果
 */
private int execute(Statement statement, Object parameter) throws SQLException {
  if (statement == null) {
    throw new SQLException(String.format("Error: Statement not available for SQL: %s", sql));
  }

  if (connection == null) {
    throw new SQLException(String.format("Error: Connection not available for SQL: %s", sql));
  }

  int result = 0;
  try {
    result = statement.executeUpdate(sql, (Map) parameter);
  } catch (SQLException e) {
    throw e;
  }
  return result;
}

MyBatis 处理 SQL 语句的细节

除了整体的执行过程,MyBatis 在处理 SQL 语句时还有一些细节需要注意:

  • 延迟加载 :MyBatis 会在需要执行 SQL 语句时才会去解析它,这种延迟加载的方式可以节省大量的开销。

  • 预加载 :虽然 MyBatis 会延迟加载,但在某些情况下,它也会预加载一些 SQL 语句,以便在应用程序启动时立即执行这些 SQL 语句。

  • 延迟加载与预加载之间的权衡 :MyBatis 会在权衡利弊的基础上,选择是否预加载 SQL 语句。一般来说,如果 SQL 语句执行的频率不高,或者 SQL 语句很简单,那么 MyBatis 就不会预加载它。

  • SQL 语句的映射 :MyBatis 会将 SQL 语句映射到 Java 对象上,以便在执行 SQL 语句后能够轻松地将结果转换为 Java 对象。

  • SQL 语句的优化 :MyBatis 会对 SQL 语句进行优化,以减少执行时间。

MyBatis 核心源码分析

/**
 * 解析 SQL 片段
 */
private Map<String, String> parseSqlFragment(Node node) {
  // 这里省略了部分代码

  return sqlFragments;
}

/**
 * 组建参数映射
 */
private void buildParameterMappings(String sql) {
  // 这里省略了部分代码
}

/**
 * 组建 MappedStatement
 */
private void buildMappedStatement() {
  // 这里省略了部分代码

  return result;
}

/**
 * 执行 SQL 语句
 */
private int execute(Statement statement, Object parameter) throws SQLException {
  // 这里省略了部分代码

  return result;
}

结语

MyBatis 的 SQL 语句创建过程是比较复杂的,涉及到多个步骤和细节。但是,一旦你掌握了这个过程,你就能更好地理解 MyBatis 是如何处理 SQL 语句的,从而能够更轻松地使用 MyBatis 来构建持久层。