Mybatis工具—SQL执行器(ScriptRunner)

  Mybatis为了统一读取SQL脚本文件中的SQL语句并执行,所以提供了ScriptRunner工具类(org.apache.ibatis.jdbc.ScriptRunner)。使用该类可以读取和执行SQL脚本,示例如下:

try{
    Connection connection = DriverManager.getConnection("数据库连接");
    ScriptRunner scriptRunner = new ScriptRunner(connection);
    scriptRunner.runScript(Resources.getResourceAsReader("mysql.sql"));
} catch(Exception e){
    // 异常
}

概述

  从Mybatis源代码上来看ScriptRunner类没有继承任何父类,没有实现任何接口,是一个单纯的工具类。从上述示例代码可以看出,构造ScriptRunner需要先构造一个Connection对象,之所以需要Connection,是因为ScriptRunner类在读取解析完SQL脚本文件之后,会执行解析得到的SQL语句。为了进一步了解ScriptRunner类,首先看一下ScriptRunner类中定义的一些属性值。

public class ScriptRunner {
  // SQL异常是否中断程序执行
  private boolean stopOnError;
  // 是否抛出SQLWarning  
  private boolean throwWarning;
  // 设置是否自动提交  
  private boolean autoCommit;
  // true:批量执行文件中的SQL语句
  // false:逐条执行SQL语句,默认情况下,以分号分隔  
  private boolean sendFullScript;
  // 是否去除SQL语句中的Windows系统里的\r换行符
  private boolean removeCRs;
  // 设置Statement中的escapeProcessing属性  
  private boolean escapeProcessing = true;
  // 日志输出
  private PrintWriter logWriter = new PrintWriter(System.out);
  // 错误日志输出  
  private PrintWriter errorLogWriter = new PrintWriter(System.err);
  // 脚本文件SQL语句的分隔符,默认为;(分号)  
  private String delimiter = DEFAULT_DELIMITER;
  // 是否支持SQL语句分割符  
  private boolean fullLineDelimiter;
}

  以上这些属性Mybatis都提供了对应的Setter方法来控制ScriptRunner工具类执行SQL脚本的行为,上述仅是对这些属性做了简单的介绍,后续会结合实际再进一步分析讲解各个属性。

runScript()方法

  ScriptRunner类中仅提供了一个runScript()方法用于执行SQL脚本文件,runScript()方法也是其最为核心的方法。

public void runScript(Reader reader) {
    setAutoCommit();
    
    try {
      if (sendFullScript) {
        executeFullScript(reader);
      } else {
        executeLineByLine(reader);
      }
    } finally {
      rollbackConnection();
    }
  }

  从mybatis源代码来看runScript一共做了以下几件事:

  1. 调用setAutoCommit()方法设置autoCommit为true,开启自动提交;
  2. 判断sendFullScript属性值,如果值为true,调用executeFullScript()方法一次性读取SQL脚本中的所有内容,如果值为false,调用executeLineByLine()方法逐行读取SQL脚本中的内容,以分号作为一条SQL语句结束的标志;
  3. 最后调用rollbackConnection()方法来rollbackConnection。

executeFullScript()方法

private void executeFullScript(Reader reader) {
    StringBuilder script = new StringBuilder();
    try {
      BufferedReader lineReader = new BufferedReader(reader);
      String line;
      while ((line = lineReader.readLine()) != null) {
        script.append(line);
        script.append(LINE_SEPARATOR);
      }
      String command = script.toString();
      println(command);
      executeStatement(command);
      commitConnection();
    } catch (Exception e) {
      String message = "Error executing: " + script + ".  Cause: " + e;
      printlnError(message);
      throw new RuntimeSqlException(message, e);
    }
}

  先来看看executeFullScript()方法内部的逻辑细节,从Mybatis源代码来看executeFullScript()方法一共做了如下几件事:

  1. 循环读取SQL脚本中的SQL语句,并将读取到的SQL语句组装放到一个StringBuilder类中;
  2. 调用println()方法打印SQL语句,println其内部使用到ScriptRunner类的logWriter属性值;
  3. 调用executeStatement(String command)方法执行SQL语句;
  4. 调用commitConnection()方法,如果ScriptRunner类的autoCommit属性值为false,就手动调用connection.commit()方法提交事务;
  5. 如果在以上整个执行过程的发生任务异常,就会调用printlnError()方法来打印异常信息。

executeLineByLine()方法

private void executeLineByLine(Reader reader) {
    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()方法内部的逻辑细节,从Mybatis源代码来看executeLineByLine()方法,该方法中对脚本中的内容逐行读取,然后调用handleLine()方法处理每行读取的内容。其核心逻辑详见handleLine方法,

private void handleLine(StringBuilder command, String line) throws SQLException {
    String trimmedLine = line.trim();
    if (lineIsComment(trimmedLine)) {
      // 判断该行是否是注释
      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);
      println(command);
      executeStatement(command.toString());
      // 重置StringBuilder
      command.setLength(0);
    } else if (trimmedLine.length() > 0) {
      // 该行不包含分号,
      // 这条SQL语句未结束,将当前行追加到StringBuilder
      command.append(line);
      command.append(LINE_SEPARATOR);
    }
}

  从Mybatis源代码来看,

  • 首先判断该行是否是SQL注释语法,如果该行是注释就直接调用println()方法打印该行语句;
  • 然后如果该行不是注释,则判断该行是否包含分号。如包含分号就说明是一条完整的SQL语句,接着做出如下处理:
    1. 获取分号之前的SQL语句
    2. 添加LINE_SEPARATOR属性对应的System.lineSeparator()的分隔符
    3. 打印SQL语句
    4. 调用executeStatement()方法执行SQL语句
    5. 重置当前使用的StringBuilder类
  • 最后该行既不是注释,也不是完整的SQL语句。就说明该条SQL语句没有结束,将对应的SQL语句追加到StringBuilder类。

你可能感兴趣的:(mybatis,mybatis,sql,java)