最近项目中想要添入启动制动完成数据库配置的功能,刚开始想到的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方法中调用上边的方法,完美实现了启动部署数据库。
希望各位大牛能指出错误地方!!!谢谢!!!!