MyBatis源码解读 - 使用ScriptRunner执行SQL脚本

思考:如果让你去实现 运行脚本文件中的SQL 功能,你会怎样实现呢?

简介

ScriptRunner工具类用于读取脚本文件中的SQL语句并执行。

开发环境

  • JDK1.8
  • MyBatis3.5.7
  • MySQL8

简单示例

Maven 依赖


    org.mybatis
    mybatis
    3.5.7


    mysql
    mysql-connector-java
    8.0.29

废话不多说,下面是一个使用ScriptRunner执行SQL脚本的简单示例,代码如下:

try {
    // 数据库连接
    Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC", "root", "abc123");
    // 创建ScriptRunner对象
    ScriptRunner scriptRunner = new ScriptRunner(connection);
    // 读取classpath路径下的文件,返回Reader对象
    Reader reader = Resources.getResourceAsReader("schema.sql");
    // 执行SQL脚本
    scriptRunner.runScript(reader);
} catch (Exception e) {
    e.printStackTrace();
}

简单细说一下,使用ScriptRunner执行SQL脚本的操作步骤:

(1)创建数据库连接,创建ScriptRunner对象时要用;

(2)调用构造方法创建ScriptRunner对象,此构造方法需要一个java.sql.Connection对象作为参数,在(1)中我们已经创建;

(3)读取 classpath 路径下的脚本文件内容,返回Reader对象;

(4)调用ScriptRunner对象的runScript()方法执行脚本,参数为(3)中创建的Reader对象。

runScript()方法源码:

public void runScript(Reader reader) {
  // 设置事务是否自动提交
  setAutoCommit();

  try {
    // 是否一次性批量执行文件中的所有SQL语句
    if (sendFullScript) {
      // 一次性批量执行文件中的所有SQL语句
      executeFullScript(reader);
    } else {
      // 逐条执行文件中的所有SQL语句
      executeLineByLine(reader);
    }
  } finally {
    rollbackConnection();
  }
}

runScript()方法调用setAutoCommit(),根据autoCommit属性的值设置事务是否自动提交,然后判断sendFullScript属性的值。如果sendFullScript值为 true,则一次性批量执行脚本文件中的所有 SQL 语句;如果sendFullScript值为 false,则逐条执行脚本文件中的 SQL 语句。

sendFullScript属性的值默认为 false,这里是逐条执行脚本文件中的 SQL 语句。

executeLineByLine()方法源码:

private void executeLineByLine(Reader reader) {
  // 要执行的SQL语句
  StringBuilder command = new StringBuilder();
  try {
    BufferedReader lineReader = new BufferedReader(reader);
    String line;
    while ((line = lineReader.readLine()) != null) {
      // 处理每行内容
      handleLine(command, line);
    }
    commitConnection();
    checkForMissingLineTerminator(command);
  } catch (Exception e) {
    String message = "Error executing: " + command + ".  Cause: " + e;
    printlnError(message);
    throw new RuntimeSqlException(message, e);
  }
}

executeLineByLine()方法逐行读取脚本文件的内容,然后调用handleLine()方法处理每行内容。

handleLine()方法源码:

private void handleLine(StringBuilder command, String line) throws SQLException {
  String trimmedLine = line.trim();
  if (lineIsComment(trimmedLine)) { // 判断该行是否是SQL注释
    Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
    if (matcher.find()) {
      delimiter = matcher.group(5);
    }
    println(trimmedLine);
  } else if (commandReadyToExecute(trimmedLine)) { // 判断该行是否包含分号
    // 获取该行分号之前的内容,追加到当前SQL语句
    command.append(line, 0, line.lastIndexOf(delimiter));
    // 追加行分隔符
    command.append(LINE_SEPARATOR);
    // 打印当前SQL语句
    println(command);
    // 执行当前SQL语句
    executeStatement(command.toString());
    // 清空SQL语句
    command.setLength(0);
  } else if (trimmedLine.length() > 0) { // 该行不包含分号,即当前SQL语句未结束
    // 追加当前行内容
    command.append(line);
    // 追加行分隔符
    command.append(LINE_SEPARATOR);
  }
}

handleLine()方法的逻辑:

MyBatis源码解读 | 使用ScriptRunner执行SQL脚本

handleLine()方法逻辑

参考

《MyBatis3源码深度解析》,江荣波

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章