修复 testcontainers 初始化脚本中的 Mysql 定界符语法错误
fixing Mysql Delimiter syntax error in testcontainers init script
我想用测试容器测试一个存储过程。因此,我使用包含存储过程定义的 SQL 脚本初始化我的容器,但出现此语法错误
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initializer' defined in com.api.bankApi.services.TransactionServiceTest$TestConfig: Invocation of init method failed; nested exception is org.springframework.data.r2dbc.connectionfactory.init.ScriptStatementFailedException: Failed to execute SQL script statement #7 of class path resource [schema.sql]: DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) ) exitSub:BEGIN DECLARE EXIT HANDLER for SQLEXCEPTION BEGIN DECLARE errNom INT UNSIGNED DEFAULT 0; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1064] [42000] 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
Caused by: org.springframework.data.r2dbc.connectionfactory.init.ScriptStatementFailedException: Failed to execute SQL script statement #7 of class path resource [schema.sql]: DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) ) exitSub:BEGIN DECLARE EXIT HANDLER for SQLEXCEPTION BEGIN DECLARE errNom INT UNSIGNED DEFAULT 0; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1064] [42000] 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
Caused by: io.r2dbc.spi.R2dbcBadGrammarException: 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
来自 mysql 文档,分隔符语法似乎是正确的我想知道可能是什么问题
这是初始化代码
@TestConfiguration
static class TestConfig {
@Bean
public ConnectionFactoryInitializer initializer() {
ConnectionFactoryOptions options = MySQLR2DBCDatabaseContainer.getOptions(database);
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(ConnectionFactories.get(options));
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}
注意:当我删除存储过程代码时,脚本 运行 没问题。并且存储过程 运行 在我的本地数据库中也很好
脚本
DROP TABLE IF EXISTS `bank_account`;
CREATE TABLE `bank_account` (
`id` int NOT NULL AUTO_INCREMENT,
`balance` decimal(17,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
DROP TABLE IF EXISTS `transactions`;
CREATE TABLE `transactions` (
`id` int NOT NULL AUTO_INCREMENT,
`transaction_type` varchar(255) DEFAULT NULL,
`reference` varchar(255) DEFAULT NULL,
`amount` decimal(17,2) DEFAULT NULL,
`creation_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
DROP TABLE IF EXISTS `error_log`;
CREATE TABLE `error_log` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`date_in` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`corrected_by` varchar(255) NOT NULL DEFAULT '',
`proc_name` varchar(50) NOT NULL DEFAULT '',
`node_id` int NOT NULL DEFAULT '-1',
`err_no` int NOT NULL DEFAULT '0',
`err_text` varchar(255) DEFAULT '',
`row_no` int NOT NULL DEFAULT '0',
`value1` varchar(255) DEFAULT '',
`value2` varchar(255) DEFAULT '',
`text1` varchar(255) DEFAULT '',
`text2` varchar(255) DEFAULT '',
`long_text` mediumtext,
PRIMARY KEY (`id`) USING BTREE,
KEY `err_log_time_idx` (`date_in`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
DELIMITER $$
CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) )
exitSub:BEGIN
DECLARE EXIT HANDLER for SQLEXCEPTION
BEGIN
DECLARE errNom INT UNSIGNED DEFAULT 0;
DECLARE errText VARCHAR (50);
GET DIAGNOSTICS CONDITION 1 errText = MESSAGE_TEXT, errNom = MYSQL_ERRNO;
INSERT INTO error_log(proc_name,err_no,err_text,row_no,value1,long_text)
VALUES ('make_transaction',errNom,errText,rowNo,concat('trans_id:',trans_id),'EXCEPTION');
END;
if v_amount < 0 then select 'negative amounts not allowed' as description; LEAVE exitSub; end if;
if not exists(select `reference` from transactions where `reference`=trans_id) then
insert into transactions (transaction_type,amount,`reference`)
values(trans_type,v_amount,trans_id);
update bank_account set balance = if(trans_type='DEPOSIT',balance+v_amount,balance-v_amount);
select 'success' as description;
LEAVE exitSub;
end if;
select 'dupicate transaction' as description;
END $$
DELIMITER ;
编辑:
经过研究,我使用了 testcontainers initscript,它现在正在执行而不会中断,但现在似乎所有其他语句都在执行,除了创建存储过程。因为当我 运行 我得到的测试 PROCEDURE does not exist error for tests that execute the procedure.
private static final MySQLContainer database = new MySQLContainer("mysql:8");
static {
try {
final String script = Resources.toString(Resources.getResource("schema.sql"), Charsets.UTF_8);
database.start();
ScriptUtils.executeDatabaseScript(new JdbcDatabaseDelegate(database,""), "schema.sql", script, false, false, DEFAULT_COMMENT_PREFIX,
DEFAULT_STATEMENT_SEPARATOR, "$$", "$$$");
} catch (Exception ex) {
Logger.getLogger(TransactionServiceTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
失败的测试错误
TransactionServiceTest.depositFundsSuccess expectation "assertNext" failed (expected: onNext();
actual:onError(org.springframework.r2dbc.BadSqlGrammarException:
executeMany; bad SQL grammar [call make_transaction(?,?,?)];
nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1305] [42000] PROCEDURE test.make_transaction does not exist))
所以最好的办法是将sql文件复制到容器中,在容器初始化时执行/docker-entrypoint-initdb.d。这是 sql 团队的推荐。这就是实现它的方法。
private static final MySQLContainer database = new MySQLContainer("mysql:8");
static {
try {
database.withCopyFileToContainer(MountableFile.forClasspathResource("schema2.sql"), "/docker-entrypoint-initdb.d/schema.sql");
database.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
我想用测试容器测试一个存储过程。因此,我使用包含存储过程定义的 SQL 脚本初始化我的容器,但出现此语法错误
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initializer' defined in com.api.bankApi.services.TransactionServiceTest$TestConfig: Invocation of init method failed; nested exception is org.springframework.data.r2dbc.connectionfactory.init.ScriptStatementFailedException: Failed to execute SQL script statement #7 of class path resource [schema.sql]: DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) ) exitSub:BEGIN DECLARE EXIT HANDLER for SQLEXCEPTION BEGIN DECLARE errNom INT UNSIGNED DEFAULT 0; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1064] [42000] 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
Caused by: org.springframework.data.r2dbc.connectionfactory.init.ScriptStatementFailedException: Failed to execute SQL script statement #7 of class path resource [schema.sql]: DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) ) exitSub:BEGIN DECLARE EXIT HANDLER for SQLEXCEPTION BEGIN DECLARE errNom INT UNSIGNED DEFAULT 0; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1064] [42000] 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
Caused by: io.r2dbc.spi.R2dbcBadGrammarException: 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 'DELIMITER $$ CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_am' at line 1
来自 mysql 文档,分隔符语法似乎是正确的我想知道可能是什么问题
这是初始化代码
@TestConfiguration
static class TestConfig {
@Bean
public ConnectionFactoryInitializer initializer() {
ConnectionFactoryOptions options = MySQLR2DBCDatabaseContainer.getOptions(database);
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(ConnectionFactories.get(options));
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}
注意:当我删除存储过程代码时,脚本 运行 没问题。并且存储过程 运行 在我的本地数据库中也很好
脚本
DROP TABLE IF EXISTS `bank_account`;
CREATE TABLE `bank_account` (
`id` int NOT NULL AUTO_INCREMENT,
`balance` decimal(17,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
DROP TABLE IF EXISTS `transactions`;
CREATE TABLE `transactions` (
`id` int NOT NULL AUTO_INCREMENT,
`transaction_type` varchar(255) DEFAULT NULL,
`reference` varchar(255) DEFAULT NULL,
`amount` decimal(17,2) DEFAULT NULL,
`creation_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
DROP TABLE IF EXISTS `error_log`;
CREATE TABLE `error_log` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`date_in` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`corrected_by` varchar(255) NOT NULL DEFAULT '',
`proc_name` varchar(50) NOT NULL DEFAULT '',
`node_id` int NOT NULL DEFAULT '-1',
`err_no` int NOT NULL DEFAULT '0',
`err_text` varchar(255) DEFAULT '',
`row_no` int NOT NULL DEFAULT '0',
`value1` varchar(255) DEFAULT '',
`value2` varchar(255) DEFAULT '',
`text1` varchar(255) DEFAULT '',
`text2` varchar(255) DEFAULT '',
`long_text` mediumtext,
PRIMARY KEY (`id`) USING BTREE,
KEY `err_log_time_idx` (`date_in`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT;
DELIMITER $$
CREATE PROCEDURE make_transaction(IN trans_type varchar(45),IN v_amount decimal(17,2),IN trans_id varchar(255) )
exitSub:BEGIN
DECLARE EXIT HANDLER for SQLEXCEPTION
BEGIN
DECLARE errNom INT UNSIGNED DEFAULT 0;
DECLARE errText VARCHAR (50);
GET DIAGNOSTICS CONDITION 1 errText = MESSAGE_TEXT, errNom = MYSQL_ERRNO;
INSERT INTO error_log(proc_name,err_no,err_text,row_no,value1,long_text)
VALUES ('make_transaction',errNom,errText,rowNo,concat('trans_id:',trans_id),'EXCEPTION');
END;
if v_amount < 0 then select 'negative amounts not allowed' as description; LEAVE exitSub; end if;
if not exists(select `reference` from transactions where `reference`=trans_id) then
insert into transactions (transaction_type,amount,`reference`)
values(trans_type,v_amount,trans_id);
update bank_account set balance = if(trans_type='DEPOSIT',balance+v_amount,balance-v_amount);
select 'success' as description;
LEAVE exitSub;
end if;
select 'dupicate transaction' as description;
END $$
DELIMITER ;
编辑:
经过研究,我使用了 testcontainers initscript,它现在正在执行而不会中断,但现在似乎所有其他语句都在执行,除了创建存储过程。因为当我 运行 我得到的测试 PROCEDURE does not exist error for tests that execute the procedure.
private static final MySQLContainer database = new MySQLContainer("mysql:8");
static {
try {
final String script = Resources.toString(Resources.getResource("schema.sql"), Charsets.UTF_8);
database.start();
ScriptUtils.executeDatabaseScript(new JdbcDatabaseDelegate(database,""), "schema.sql", script, false, false, DEFAULT_COMMENT_PREFIX,
DEFAULT_STATEMENT_SEPARATOR, "$$", "$$$");
} catch (Exception ex) {
Logger.getLogger(TransactionServiceTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
失败的测试错误
TransactionServiceTest.depositFundsSuccess expectation "assertNext" failed (expected: onNext();
actual:onError(org.springframework.r2dbc.BadSqlGrammarException:
executeMany; bad SQL grammar [call make_transaction(?,?,?)];
nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [1305] [42000] PROCEDURE test.make_transaction does not exist))
所以最好的办法是将sql文件复制到容器中,在容器初始化时执行/docker-entrypoint-initdb.d。这是 sql 团队的推荐。这就是实现它的方法。
private static final MySQLContainer database = new MySQLContainer("mysql:8");
static {
try {
database.withCopyFileToContainer(MountableFile.forClasspathResource("schema2.sql"), "/docker-entrypoint-initdb.d/schema.sql");
database.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}