MyBatis StatementHandler是如何创建 Statement 对象的? 如何执行 SQL 语句?

发布于:2025-03-23 ⋅ 阅读:(40) ⋅ 点赞:(0)

在MyBatis中,StatementHandler负责创建Statement对象并执行SQL语句。以下是其具体流程:
1. StatementHandler.prepare() 方法:创建 JDBC Statement 对象

StatementHandler.prepare(Connection connection, Integer transactionTimeout) 方法是核心方法,负责基于 MappedStatement 配置创建相应的 JDBC Statement 对象。

以下是 prepare() 方法内部发生步骤的分解:

  • 输入参数:

    • Connection connection:将用于创建 Statement 的 JDBC Connection 对象。此 Connection 通常由 MyBatis 的 Transaction 组件管理。
    • Integer transactionTimeout:一个可选的事务超时值(以秒为单位),用于 Statement
  • 确定 Statement 类型:
    StatementHandler 检查其持有的 MappedStatement 对象以确定 statementTypestatementType 在 Mapper XML 文件中通过 <select><insert><update><delete> 元素中的 statementType 属性进行配置。可能的取值包括:

    • STATEMENT:用于普通的 java.sql.Statement
    • PREPARED:用于 java.sql.PreparedStatement(默认且最常用)。
    • CALLABLE:用于 java.sql.CallableStatement(用于存储过程/函数)。
  • 创建 JDBC Statement 对象:
    基于确定的 statementTypeStatementHandler 使用提供的 Connection 对象来创建相应的 JDBC Statement 实例:

    • statementType="STATEMENT" (SimpleStatementHandler):

      Statement statement = connection.createStatement(); // 使用 Connection.createStatement()
      

      SimpleStatementHandler 使用 connection.createStatement() 创建一个普通的 Statement 对象。 这是最简单的类型,适用于没有参数的基本 SQL。

    • statementType="PREPARED" (PreparedStatementHandler - 默认):

      PreparedStatement statement = connection.prepareStatement(boundSql.getSql()); // 使用 Connection.prepareStatement(sql)
      

      PreparedStatementHandler 使用 connection.prepareStatement(boundSql.getSql())。 这至关重要:

      • connection.prepareStatement(boundSql.getSql()): 它使用 Connection.prepareStatement() 创建一个 PreparedStatement。 重要的是,它将 boundSql.getSql() 作为 SQL 字符串传递给 prepareStatement()boundSql.getSql() 包含最终的、处理后的 SQL 语句,在动态 SQL 评估之后,参数占位符(?)已就位。
      • PreparedStatement 用于性能和安全性: PreparedStatement 用于提高性能(数据库预编译)和安全性(通过使用参数占位符防止 SQL 注入)。
    • statementType="CALLABLE" (CallableStatementHandler):

      CallableStatement statement = connection.prepareCall(boundSql.getSql()); // 使用 Connection.prepareCall(sql)
      

      CallableStatementHandler 使用 connection.prepareCall(boundSql.getSql()) 创建一个 CallableStatement,同样传递处理后的 SQL (boundSql.getSql()),这将是存储过程/函数的调用语法。

  • 设置查询超时(可选):
    如果提供了 transactionTimeout 值(可以在 MyBatis 配置中配置),StatementHandler 将其设置为新创建的 Statement 的查询超时时间:

    if (transactionTimeout != null) {
        statement.setQueryTimeout(transactionTimeout); // 设置 JDBC Statement 超时时间
    }
    

    这有助于防止长时间运行的查询无限期挂起并消耗资源。

  • 返回 Statement 对象:
    最后,prepare() 方法返回创建的 JDBC Statement 对象(StatementPreparedStatementCallableStatement)。 这个 Statement 对象现在已准备好进行参数化和执行。

2. StatementHandler 执行 SQL 语句的方法

一旦 Statement 对象准备就绪,StatementHandler 提供了实际执行 SQL 的方法:query()update()(以及 BatchExecutorbatch()):

  • StatementHandler.query(Statement statement, ResultHandler resultHandler):执行 SELECT 查询

    • 输入参数:

      • Statement statement:JDBC Statement 对象(在 prepare() 中创建)。
      • ResultHandler resultHandler:负责处理 ResultSetResultHandler
    • 执行查询:

      statement.executeQuery(); // 使用 Statement.executeQuery()
      

      StatementHandler 调用 statement.executeQuery() 来执行 SQL 查询。 executeQuery() 专门用于 SELECT 语句,并返回 ResultSet

    • 处理 ResultSet

      ResultSet resultSet = statement.getResultSet();
      resultHandler.handleResultSets(resultSet); // 将 ResultSet 处理委托给 ResultHandler
      
      • statement.getResultSet():检索查询生成的 ResultSet
      • resultHandler.handleResultSets(resultSet)至关重要的是,StatementHandlerResultSet 的实际处理和映射委托给 ResultHandlerResultHandler 迭代 ResultSet,并使用 ResultMap(或 resultType)将每一行映射到 Java 对象。
    • 返回结果列表:
      query() 方法返回 ResultHandlerResultSet 处理期间填充的 List<E>

  • StatementHandler.update(Statement statement):执行 INSERTUPDATEDELETE 语句

    • 输入参数:

      • Statement statement:JDBC Statement 对象。
    • 执行更新:

      statement.executeUpdate(); // 使用 Statement.executeUpdate()
      

      StatementHandler 调用 statement.executeUpdate() 来执行 INSERTUPDATEDELETE 语句。 executeUpdate() 用于 DML(数据操作语言)语句,这些语句修改数据并返回受影响的行数。

    • 返回更新计数:

      return statement.getUpdateCount(); // 获取受影响的行数
      

      update() 方法返回 statement.getUpdateCount() 返回的整数值,该值表示 INSERTUPDATEDELETE 操作影响的行数。

  • StatementHandler.batch(Statement statement):执行批量语句(用于 BatchExecutor

    • 输入参数:

      • Statement statement:JDBC Statement 对象。
    • 添加到批处理(多次调用):
      对于 BatchExecutor,在调用 batch() 之前,会处理多个 SQL 语句(由 BoundSql 对象表示)。 对于批处理中的每个 SQL 语句,都会调用 parameterize() 方法来设置 Statement 上的参数,然后:

      preparedStatement.addBatch(); // 添加到 JDBC 批处理
      

      BatchExecutor 使用 preparedStatement.addBatch()PreparedStatement 添加到 JDBC 批处理中。

    • 执行批处理(调用一次):
      在将所有 SQL 语句添加到批处理之后,调用 batch() 方法:

      statement.executeBatch(); // 执行 JDBC 批处理
      

      StatementHandler 调用 statement.executeBatch() 以在单个数据库往返中执行批处理队列中的所有批处理 SQL 语句。

    • 返回批处理结果:

      return statement.getBatchResult(); // 获取批处理结果
      

      batch() 方法检索批处理执行的结果(通常是更新计数或异常的数组),并使用 statement.getBatchResult() 将其作为 List<BatchResult> 返回。

总结:

StatementHandler 充当 MyBatis 和原始 JDBC API 之间的桥梁。

  1. 创建不同类型的 Statement 对象StatementPreparedStatementCallableStatement),基于 MappedStatement 配置,使用 JDBC Connection 方法(createStatement()prepareStatement()prepareCall())。
  2. 使用 Statement.executeQuery() 执行 SQL 查询,并将 ResultSet 处理委托给 ResultHandler
  3. 使用 Statement.executeUpdate() 执行 SQL 更新,并返回更新计数。
  4. 使用 Statement.addBatch()Statement.executeBatch() 执行批处理 SQL 语句(在 BatchExecutor 中)。