使用MyBatis中的ScriptRunner來執行sql檔案指令碼,實現啟動自動部署資料庫
最近專案中想要添入啟動制動完成資料庫配置的功能,剛開始想到的ANT方式,但是放棄了(凡是放棄的,根本原因是:我不會........);所以最後採用了ScriptRunner來執行,這個方法無論是DML還是DDL都可以執行,但是有幾點比較坑~~,咱們後話說。
首先我們介紹下當前的環境:SpringBoot2.0.2+MySql;
首先建立資料庫連線,配置基本的url,drive,username和password,不多說,基礎寫法:
Connection conn = null; try{ Class.forName(driver); conn = DriverManager.getConnection(url, userName, password); }catch(Exception e){ conn.rollback(); _log.error("資料回滾成功"); }
然後我們將Connection設定成不自動提交,這樣才可以使用conn.rollback回滾功能;這點要注意。
接下來宣告一個ScriptRunner來建立基本的讀取規則;基本設定程式碼註釋見:
ScriptRunner runner = new ScriptRunner(conn); // 設定不自動提交 runner.setAutoCommit(false); /* * setStopOnError引數作用:遇見錯誤是否停止; * (1)false,遇見錯誤不會停止,會繼續執行,會列印異常資訊,並不會丟擲異常,當前方法無法捕捉異常無法進行回滾操作, * 無法保證在一個事務內執行; (2)true,遇見錯誤會停止執行,列印並丟擲異常,捕捉異常,並進行回滾,保證在一個事務內執行; */ runner.setStopOnError(true); /* * 按照那種方式執行 方式一:true則獲取整個指令碼並執行; 方式二:false則按照自定義的分隔符每行執行; */ runner.setSendFullScript(false); // 設定是否輸出日誌,null不輸出日誌,不設定自動將日誌輸出到控制檯 runner.setLogWriter(null);
接下來使用方法runScript來讀取SQL指令碼就可以了,具體的執行,在runner點方法可以檢視,我在這裡使用runScript來讀取執行File檔案,例如:
runner.runScript(Resources.getResourceAsReader("table/****/****.sql"));
這裡要注意,我這裡使用Resources.getResourceAsReader(String source);這個方法,這個方法可以返回一個File檔案,不過,他只能讀取classpath路徑下的檔案,也就是說,如果你只是單純的將指令碼放到工程的根目錄下是無法讀取到的,這裡可以將sql資料夾新增到ClassPath路徑下,具體的新增方法如下:
在使用Eclipse的通知可以右鍵工程-->Preferences-->Java Build Path-->Source下,如圖:
點選Add Folder....按鈕,如圖,將SQL資料夾前面打上對號,表示將此檔案及子檔案加入classpath:
此時根據路徑就可以實現讀取指令碼。
如果要執行多個SQL檔案,可以重複使用runScript方法來執行,我這裡因為寫死了,並且有先後規定,所以寫了個switch下面的是完整的方法:
package com.dcjt518.oms.common.conf;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @{自動讀取SQL語句}
* @author 建立人: LuNanTing
* @Title 標 題: DataSourceTest.java
* @update 修改人:LuNanTing
* @date 修改事件:2018年11月1日
*/
public class DataScriptExecution {
private String driver;
private String url;
private String userName;
private String password;
private final Logger _log = LoggerFactory.getLogger(DataScriptExecution.class);
Exception error = null;
public void test() {
driver = "com.mysql.jdbc.Driver";
url = "jdbc:mysql://127.0.0.1:3306/DcOmsTest?characterEncoding=utf-8&useUnicode=true";
userName = "root";
password = "123456";
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, userName, password);
// 設定不自動提交
conn.setAutoCommit(false);
ScriptRunner runner = new ScriptRunner(conn);
// 設定不自動提交
runner.setAutoCommit(false);
/*
* setStopOnError引數作用:遇見錯誤是否停止;
* (1)false,遇見錯誤不會停止,會繼續執行,會列印異常資訊,並不會丟擲異常,當前方法無法捕捉異常無法進行回滾操作,
* 無法保證在一個事務內執行; (2)true,遇見錯誤會停止執行,列印並丟擲異常,捕捉異常,並進行回滾,保證在一個事務內執行;
*/
runner.setStopOnError(true);
/*
* 按照那種方式執行 方式一:true則獲取整個指令碼並執行; 方式二:false則按照自定義的分隔符每行執行;
*/
runner.setSendFullScript(false);
// 設定是否輸出日誌,null不輸出日誌,不設定自動將日誌輸出到控制檯
runner.setLogWriter(null);
// 如果又多個sql檔案,可以寫多個runner.runScript(xxx),
Resources.setCharset(Charset.forName("UTF8"));
int i = 0;
while (i != -1) {
switch (i) {
case 0 :
i++;
// 定義命令間的分隔符
runner.setDelimiter(";");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test1.sql"));
break;
case 1 :
i++;
// 定義命令間的分隔符
runner.setDelimiter(";");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test2.sql"));
break;
case 2 :
i++;
// 定義命令間的分隔符
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test3.sql"));
break;
case 3 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test4.sql"));
break;
case 4 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test5.sql"));
break;
case 5 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test6.sql"));
break;
case 6 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test7.sql"));
break;
case 7 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test8.sql"));
break;
case 8 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test9.sql"));
break;
case 9 :
i++;
runner.setDelimiter(";;");
runner.setFullLineDelimiter(false);
runner.runScript(Resources.getResourceAsReader("table/****/test10.sql"));
break;
default :
i = -1;
continue;
}
}
} catch (Exception e) {
try {
conn.rollback();
_log.error("資料回滾成功");
} catch (SQLException e1) {
_log.error("資料回滾失敗,系統錯誤");
}
_log.error("執行sql檔案進行資料庫建立失敗....", e);
error = e;
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
error = e;
}
}
if (error != null) {
try {
throw error;
} catch (Exception e) {
_log.error(error.getLocalizedMessage());
}
} else {
_log.info("SQL指令碼執行完成");
}
}
}
相信大家也看出來了,在每次執行的時候,我都要重新定義分隔符,尤其是儲存過程中,他是無法按照普通的DDL來執行的,因為,在MySql中預設分隔符是;,這就導致每次執行儲存過程的時候就會報錯,最後error定位在分隔符上,無奈只有改變儲存過程的分割方式,讓他遇到;;來結束,但這樣的指令碼只能在專案中執行,如果放到Navicat中是沒有辦法執行的,於是在儲存過程的指令碼頭部加入//delimiter ;; ;這樣雙方都能看得懂,也能單獨執行了。但如果使用的SQLServer就沒有這樣麻煩的事情,因為SQLServer使用的分隔符是GO這個命令,就沒有上述那麼麻煩。這裡需要注意。
說到這裡了,既然希望能專案部署就可以執行,當然是要在Spring容器剛剛啟動的時候,或者服務容器啟動而Spring容器還未啟動的時候執行,這裡就可以用到Spring中的@WebListener註解,他是服務容器啟動監聽方法,在contextInitialized方法中呼叫上邊的方法,完美實現了啟動部署資料庫。
希望各位大牛能指出錯誤地方!!!謝謝!!!!