1. 程式人生 > >使用MyBatis中的ScriptRunner來執行sql檔案指令碼,實現啟動自動部署資料庫

使用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方法中呼叫上邊的方法,完美實現了啟動部署資料庫。

希望各位大牛能指出錯誤地方!!!謝謝!!!!