业务需求:读取固定目录下的所有SQL脚本文件然后执行到指定的数据库
在Spring中做测试有这样一个注解(Sql),如下所示:
@Sql("classpath:ms_class.sql")
@Test
public void contextLoads() {
}
这个注解就是用于读取Sql脚本并执行的,那么参照这个就可以了。
官方注释说明:
@Sql is used to annotate a test class or test method to configure SQL scripts() and statements() to be executed against a given database during integration tests.
Method-level declarations override class-level declarations.
Script execution is performed by the SqlScriptsTestExecutionListener, which is enabled by default.
The configuration options provided by this annotation and @SqlConfig are equivalent to those supported by ScriptUtils and ResourceDatabasePopulator but are a superset of those provided by the <jdbc:initialize-database/> XML namespace element. Consult the javadocs of individual attributes in this annotation and @SqlConfig for details.
Beginning with Java 8, @Sql can be used as a repeatable annotation. Otherwise, @SqlGroup can be used as an explicit container for declaring multiple instances of @Sql.
大意如下:
用于注解一个测试类或者测试方法用于在集成测试时配置在一个指定数据库上执行的SQL脚本或者执行语句。方法级别的注解会覆盖类级别的注解。
这个功能是通过SqlScriptsTestExecutionListener类来实现的,默认是开启的
可以通过注解@SqlConfig配置脚本解析的各种属性。
查看SqlScriptsTestExecutionListener类下的方法如下:
// org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener
private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel)
throws Exception {
if (executionPhase != sql.executionPhase()) {
return;
}
// 读取配置信息
MergedSqlConfig mergedSqlConfig = new MergedSqlConfig(sql.config(), testContext.getTestClass());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Processing %s for execution phase [%s] and test context %s.",
mergedSqlConfig, executionPhase, testContext));
}
// 包装各种配置并真实执行数据库操作的类
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setSqlScriptEncoding(mergedSqlConfig.getEncoding());
populator.setSeparator(mergedSqlConfig.getSeparator());
populator.setCommentPrefix(mergedSqlConfig.getCommentPrefix());
populator.setBlockCommentStartDelimiter(mergedSqlConfig.getBlockCommentStartDelimiter());
populator.setBlockCommentEndDelimiter(mergedSqlConfig.getBlockCommentEndDelimiter());
populator.setContinueOnError(mergedSqlConfig.getErrorMode() == ErrorMode.CONTINUE_ON_ERROR);
populator.setIgnoreFailedDrops(mergedSqlConfig.getErrorMode() == ErrorMode.IGNORE_FAILED_DROPS);
// 读取环境中的资源配置
String[] scripts = getScripts(sql, testContext, classLevel);
scripts = TestContextResourceUtils.convertToClasspathResourcePaths(testContext.getTestClass(), scripts);
List<Resource> scriptResources = TestContextResourceUtils.convertToResourceList(
testContext.getApplicationContext(), scripts);
for (String stmt : sql.statements()) {
if (StringUtils.hasText(stmt)) {
stmt = stmt.trim();
scriptResources.add(new ByteArrayResource(stmt.getBytes(), "from inlined SQL statement: " + stmt));
}
}
populator.setScripts(scriptResources.toArray(new Resource[scriptResources.size()]));
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL scripts: " + ObjectUtils.nullSafeToString(scriptResources));
}
// 获取数据源 事务
String dsName = mergedSqlConfig.getDataSource();
String tmName = mergedSqlConfig.getTransactionManager();
DataSource dataSource = TestContextTransactionUtils.retrieveDataSource(testContext, dsName);
PlatformTransactionManager txMgr = TestContextTransactionUtils.retrieveTransactionManager(testContext, tmName);
boolean newTxRequired = (mergedSqlConfig.getTransactionMode() == TransactionMode.ISOLATED);
if (txMgr == null) {
if (newTxRequired) {
throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: " +
"cannot execute SQL scripts using Transaction Mode [%s] without a PlatformTransactionManager.",
testContext, TransactionMode.ISOLATED));
}
if (dataSource == null) {
throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: " +
"supply at least a DataSource or PlatformTransactionManager.", testContext));
}
// Execute scripts directly against the DataSource
// 操作数据库
populator.execute(dataSource);
}
else {
DataSource dataSourceFromTxMgr = getDataSourceFromTransactionManager(txMgr);
// Ensure user configured an appropriate DataSource/TransactionManager pair.
if (dataSource != null && dataSourceFromTxMgr != null && !dataSource.equals(dataSourceFromTxMgr)) {
throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: " +
"the configured DataSource [%s] (named '%s') is not the one associated with " +
"transaction manager [%s] (named '%s').", testContext, dataSource.getClass().getName(),
dsName, txMgr.getClass().getName(), tmName));
}
if (dataSource == null) {
dataSource = dataSourceFromTxMgr;
if (dataSource == null) {
throw new IllegalStateException(String.format("Failed to execute SQL scripts for " +
"test context %s: could not obtain DataSource from transaction manager [%s] (named '%s').",
testContext, txMgr.getClass().getName(), tmName));
}
}
final DataSource finalDataSource = dataSource;
int propagation = (newTxRequired ? TransactionDefinition.PROPAGATION_REQUIRES_NEW :
TransactionDefinition.PROPAGATION_REQUIRED);
TransactionAttribute txAttr = TestContextTransactionUtils.createDelegatingTransactionAttribute(
testContext, new DefaultTransactionAttribute(propagation));
new TransactionTemplate(txMgr, txAttr).execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
populator.execute(finalDataSource);
}
});
}
}
上面代码主要做了如下几个事情:
也就是所有的核心都在以下类里面
org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
分析这个类的结构
类的属性中包含了脚本资源、文件编码、分割符(默认;)、SQL脚本的默认单行注释前缀(–)、SQL脚本的默认多行注释开头与结尾,对应源码如下:
/**
* Default statement separator within SQL scripts: {@code ";"}.
*/
public static final String DEFAULT_STATEMENT_SEPARATOR = ";";
/**
* Fallback statement separator within SQL scripts: {@code "\n"}.
* Used if neither a custom separator nor the
* {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script.
*/
public static final String FALLBACK_STATEMENT_SEPARATOR = "\n";
/**
* End of file (EOF) SQL statement separator: {@code "^^^ END OF SCRIPT ^^^"}.
* This value may be supplied as the {@code separator} to {@link
* #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String)}
* to denote that an SQL script contains a single statement (potentially
* spanning multiple lines) with no explicit statement separator. Note that
* such a script should not actually contain this value; it is merely a
* virtual statement separator.
*/
public static final String EOF_STATEMENT_SEPARATOR = "^^^ END OF SCRIPT ^^^";
/**
* Default prefix for single-line comments within SQL scripts: {@code "--"}.
*/
public static final String DEFAULT_COMMENT_PREFIX = "--";
/**
* Default start delimiter for block comments within SQL scripts: {@code "/*"}.
*/
public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*";
/**
* Default end delimiter for block comments within SQL scripts: "*/"
.
*/
public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";
也就是说通过这个类,在默认的情况下,支持读取如下的脚本:
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50727
Source Host : localhost:3306
Source Database : ms_class
Target Server Type : MYSQL
Target Server Version : 50727
File Encoding : 65001
Date: 2020-04-01 14:42:58
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for lesson
-- ----------------------------
DROP TABLE IF EXISTS `lesson`;
CREATE TABLE `lesson` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`title` varchar(45) NOT NULL COMMENT '标题',
`cover` varchar(45) NOT NULL COMMENT '课程封面',
`price` decimal(10,0) NOT NULL COMMENT '价格',
`description` varchar(500) NOT NULL,
`create_time` datetime NOT NULL COMMENT '创建时间',
`video_url` varchar(255) NOT NULL COMMENT '视频播放地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of lesson
-- ----------------------------
INSERT INTO `lesson` VALUES ('1', 'SpringCloud视频教程', 'xxx', '5', 'SpringCloud视频教程', '2020-02-15 15:50:35', 'https://ke.qq.com/classroom/index.html');
-- ----------------------------
-- Table structure for lesson_user
-- ----------------------------
DROP TABLE IF EXISTS `lesson_user`;
CREATE TABLE `lesson_user` (
`lesson_id` int(11) NOT NULL COMMENT 'leson.id',
`user_id` int(11) NOT NULL COMMENT 'user.id',
PRIMARY KEY (`lesson_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of lesson_user
-- ----------------------------
INSERT INTO `lesson_user` VALUES ('1', '3');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Id',
`username` varchar(45) NOT NULL COMMENT '账号',
`password` varchar(45) NOT NULL COMMENT '密码',
`money` decimal(10,0) NOT NULL COMMENT '余额',
`role` varchar(45) NOT NULL COMMENT '角色',
`reg_time` datetime DEFAULT NULL COMMENT '注册时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('2', 'jack', '1234', '145', 'user', '2020-02-29 21:27:59');
INSERT INTO `user` VALUES ('3', 'king', '1234', '145', 'vip', '2020-02-29 21:27:59');
在这个脚本中包含SQL的块注释/**/和单行注释–
编写一个服务类接口
public interface SqlExecutorService {
/**
* 执行脚本
* @param resoureLocation 文件路径
*/
void exec(String resoureLocation);
}
实现这个接口
import java.io.IOException;
import java.util.Arrays;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.stereotype.Service;
import com.example.durid.demo.service.SqlExecutorService;
@Service
public class DefaultSqlExecutorService implements SqlExecutorService {
private static final Logger logger = LoggerFactory.getLogger(SqlExecutorService.class);
@Autowired
private DataSource dataSource;
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
@Override
public void exec(String resoureLocation) {
try {
Resource[] scripts = resourceResolver.getResources(resoureLocation);
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(scripts);
populator.setSqlScriptEncoding("utf-8");
populator.execute(dataSource);
logger.info("执行脚本{}成功", Arrays.toString(scripts));
} catch (IOException e) {
throw new RuntimeException("执行脚本" + resoureLocation + "异常", e);
}
}
}
这个服务类中使用ResourcePatternResolver去读取资源,因为这个类可以读取如下格式的资源,既可以通过匹配符模式,又可以直接指定全路径,非常方便
classpath*:config/**/*.sql
classpath*:ms_user.sql
设置使用utf-8编码读取资源,因为默认情况下ResourceDatabasePopulator使用的是当前平台使用的编码,这样会对平台依赖。
操作注入的数据源。
如果对执行细节感兴趣的话,可以去跟读源码DatabasePopulatorUtils.execute(this, dataSource),其实也没有多大的学问,主要是字符串处理,怎么把那些注释过滤掉,只保留需要执行的SQL语句。另外分析源码,也可以知道这个工具类读取脚本然后是一句一句执行的,也就是说如果想优化的话,可以考虑批量执行。
测试一下(当前是一个Spring Boot工程,此处不详细说明):
@SpringBootApplication
@MapperScans({ @MapperScan(basePackages = { "com.example.mybatis.demo.mapper" }, annotationClass = Repository.class),
@MapperScan(basePackages = { "com.example.durid.demo.mapper" }, annotationClass = Mapper.class) })
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Autowired
private SqlExecutorService sqlExecutorService;
@Override
public void run(String... args) throws Exception {
sqlExecutorService.exec("classpath:*.sql");
}
}
然后在application.properties文件中开启对应的日志
logging.level.org.springframework.jdbc.datasource.init=debug
运行Spring Boot主类,查看控制台日志,有如下信息:
2020-04-23 09:33:52.150 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from file [D:\20191030\demo\target\classes\ms_class.sql]
2020-04-23 09:33:52.153 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: SET FOREIGN_KEY_CHECKS=0
2020-04-23 09:33:52.722 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: DROP TABLE IF EXISTS `lesson`
2020-04-23 09:33:53.446 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE TABLE `lesson` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `title` varchar(45) NOT NULL COMMENT '标题', `cover` varchar(45) NOT NULL COMMENT '课程封面', `price` decimal(10,0) NOT NULL COMMENT '价格', `description` varchar(500) NOT NULL, `create_time` datetime NOT NULL COMMENT '创建时间', `video_url` varchar(255) NOT NULL COMMENT '视频播放地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
2020-04-23 09:33:53.557 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `lesson` VALUES ('1', 'SpringCloud视频教程', 'xxx', '5', 'SpringCloud视频教程', '2020-02-15 15:50:35', 'https://ke.qq.com/classroom/index.html')
2020-04-23 09:33:53.876 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: DROP TABLE IF EXISTS `lesson_user`
2020-04-23 09:33:54.274 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE TABLE `lesson_user` ( `lesson_id` int(11) NOT NULL COMMENT 'leson.id', `user_id` int(11) NOT NULL COMMENT 'user.id', PRIMARY KEY (`lesson_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
2020-04-23 09:33:54.407 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `lesson_user` VALUES ('1', '3')
2020-04-23 09:33:54.667 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: DROP TABLE IF EXISTS `user`
2020-04-23 09:33:55.324 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Id', `username` varchar(45) NOT NULL COMMENT '账号', `password` varchar(45) NOT NULL COMMENT '密码', `money` decimal(10,0) NOT NULL COMMENT '余额', `role` varchar(45) NOT NULL COMMENT '角色', `reg_time` datetime DEFAULT NULL COMMENT '注册时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户信息表'
2020-04-23 09:33:55.395 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user` VALUES ('2', 'jack', '1234', '145', 'user', '2020-02-29 21:27:59')
2020-04-23 09:33:55.480 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user` VALUES ('3', 'king', '1234', '145', 'vip', '2020-02-29 21:27:59')
2020-04-23 09:33:55.481 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from file [D:\20191030\demo\target\classes\ms_class.sql] in 3331 ms.
2020-04-23 09:33:55.481 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from file [D:\20191030\demo\target\classes\ms_user.sql]
2020-04-23 09:33:55.483 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: SET FOREIGN_KEY_CHECKS=0
2020-04-23 09:33:56.187 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: DROP TABLE IF EXISTS `user`
2020-04-23 09:33:56.703 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Id', `username` varchar(45) NOT NULL COMMENT '账号', `password` varchar(45) NOT NULL COMMENT '密码', `money` decimal(10,0) NOT NULL COMMENT '余额', `role` varchar(45) NOT NULL COMMENT '角色', `reg_time` datetime DEFAULT NULL COMMENT '注册时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户信息表'
2020-04-23 09:33:56.774 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user` VALUES ('1', 'itmuch', '1111', '3', 'user', '2020-02-15 14:37:20')
2020-04-23 09:33:56.919 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user` VALUES ('2', 'jack', '1234', '145', 'user', '2020-02-29 21:27:59')
2020-04-23 09:33:57.094 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user` VALUES ('3', 'king', '1234', '125', 'vip', '2020-02-29 21:27:59')
2020-04-23 09:33:57.413 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: DROP TABLE IF EXISTS `user_account_event_log`
2020-04-23 09:33:57.953 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 0 returned as update count for SQL: CREATE TABLE `user_account_event_log` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Id', `user_id` int(11) DEFAULT NULL COMMENT 'user.id', `money` decimal(10,0) NOT NULL COMMENT '金额', `event` varchar(20) NOT NULL COMMENT '事件', `create_time` datetime NOT NULL COMMENT '创建时间', `description` varchar(200) NOT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='用户金额流水表'
2020-04-23 09:33:58.094 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('2', '1', '5', '购买课程', '2020-02-24 09:59:03', '1购买了id为1的课程')
2020-04-23 09:33:58.144 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('3', '2', '5', '购买课程', '2020-03-01 05:22:33', '2购买了id为1的课程')
2020-04-23 09:33:58.269 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('4', '2', '5', '购买课程', '2020-03-01 05:35:17', '2购买了id为1的课程')
2020-04-23 09:33:58.352 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('5', '3', '5', '购买课程', '2020-03-11 12:54:32', '3购买了id为1的课程')
2020-04-23 09:33:58.442 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('6', '3', '5', '购买课程', '2020-03-11 13:28:22', '3购买了id为1的课程')
2020-04-23 09:33:58.488 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('7', '3', '5', '购买课程', '2020-03-15 11:05:55', '3购买了id为1的课程')
2020-04-23 09:33:58.544 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : 1 returned as update count for SQL: INSERT INTO `user_account_event_log` VALUES ('8', '3', '5', '购买课程', '2020-03-15 12:53:11', '3购买了id为1的课程')
2020-04-23 09:33:58.546 DEBUG 12744 --- [ main] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from file [D:\20191030\demo\target\classes\ms_user.sql] in 3064 ms.
2020-04-23 09:33:58.547 INFO 12744 --- [ main] c.e.d.demo.service.SqlExecutorService : 执行脚本[file [D:\20191030\demo\target\classes\ms_class.sql], file [D:\20191030\demo\target\classes\ms_user.sql]]成功
从以上信息不难看出,执行脚本ms_class.sql, ms_user.sql成功
备注:如果有一个超长的SQL语句,可能跨越多行,需要注意在后面添加分割符(;),否则会抛出异常。
此处模拟一下:在主目录下添加如下脚本:
INSERT INTO `user_account_event_log`
VALUES ('9', '3', '5', '购买课程', '2020-03-15 12:53:11', '3购买了id为1的课程')
出现如下异常:
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:787) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:768) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
at com.example.durid.demo.DemoApplication.main(DemoApplication.java:20) [classes/:na]
Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of file [D:\20191030\demo\target\classes\insert.sql]: INSERT INTO `user_account_event_log` ; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:622) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:254) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:49) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.execute(ResourceDatabasePopulator.java:269) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at com.example.durid.demo.service.impl.DefaultSqlExecutorService.exec(DefaultSqlExecutorService.java:35) ~[classes/:na]
at com.example.durid.demo.DemoApplication.run(DemoApplication.java:28) [classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784) [spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
... 5 common frames omitted
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_121]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_121]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_121]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_121]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.Util.getInstance(Util.java:408) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:943) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3973) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3909) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2486) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2444) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.StatementImpl.executeInternal(StatementImpl.java:845) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:745) ~[mysql-connector-java-5.1.42.jar:5.1.42]
at com.alibaba.druid.pool.DruidPooledStatement.execute(DruidPooledStatement.java:632) ~[druid-1.1.17.jar:1.1.17]
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:601) ~[spring-jdbc-5.2.4.RELEASE.jar:5.2.4.RELEASE]
... 11 common frames omitted
从日志也不难分析出这里将第一行作为了一条语句来执行。
通过分析源码,主要的问题在ScriptUtils.executeSqlScript这个方法中如下的代码
if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) {
separator = FALLBACK_STATEMENT_SEPARATOR;
}
当脚本中不存在默认的分割符的情况下,分割符会被更换为\n
有两种方式可以解决上面的问题,
一种是单行书写脚本
第二种是添加分割符,修改脚本如下:
INSERT INTO `user_account_event_log`
VALUES ('9', '3', '5', '购买课程', '2020-03-15 12:53:11', '3购买了id为1的课程');
执行程序,出现如下日志
2020-04-23 09:46:54.279 INFO 276 --- [ main] c.e.d.demo.service.SqlExecutorService : 执行脚本[file [D:\20191030\demo\target\classes\insert.sql], file [D:\20191030\demo\target\classes\ms_class.sql], file [D:\20191030\demo\target\classes\ms_user.sql]]成功