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

举报
汪小成 发表于 2022/10/20 15:14:55 2022/10/20
【摘要】 使用ScriptRunner执行SQL脚本文件

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

简介

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

开发环境

  • JDK1.8
  • MyBatis3.5.7
  • MySQL

简单示例

Maven 依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>

废话不多说,下面是一个使用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()方法的逻辑:

image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。