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脚本的行为,上述仅是对这些属性做了简单的介绍,后续会结合实际再进一步分析讲解各个属性。
ScriptRunner类中仅提供了一个runScript()方法用于执行SQL脚本文件,runScript()方法也是其最为核心的方法。
public void runScript(Reader reader) {
setAutoCommit();
try {
if (sendFullScript) {
executeFullScript(reader);
} else {
executeLineByLine(reader);
}
} finally {
rollbackConnection();
}
}
从mybatis源代码来看runScript一共做了以下几件事:
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()方法一共做了如下几件事:
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源代码来看,