Исправление синтаксической ошибки Mysql Delimiter в сценарии инициализации testcontainers

Я хочу протестировать хранимую процедуру с помощью testcontainers. Итак, я инициализирую свой контейнер с помощью сценария 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 ;

Обновлено:

после исследования я использовал initscript testcontainers, который теперь выполняется без прерывания, но теперь кажется, что все остальные операторы выполняются, кроме создания хранимой процедуры. потому что, когда я запускаю тесты, я получаю ошибку ПРОЦЕДУРА не существует для тестов, которые выполняют процедуру.

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))

'Если вы используете клиентскую программу mysql... вы должны переопределить разделитель' - dev.mysql.com/doc/refman/8.0/en/stored-programs-defining.htm‌​l... вы не используете клиентскую программу mysql, пробовали ли вы без разделителей?

P.Salmon 06.04.2022 10:03
DELIMITER — это инструкция SQL нет, но клиентская команда. Его не должно быть в вашем скрипте. Прочитайте руководство для вашего языка и доступ к библиотеке/фреймворку MySQL для правильного изменения разделителя в пакетном скрипте.
Akina 06.04.2022 11:21

да, я пробовал без, и это не работает. я не уверен, что r2dbc имеет эту функцию разделителя.

martin kimani 06.04.2022 13:14
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
33
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поэтому лучше всего скопировать файл 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());
        }       
    }

Это также то, что команда Testcontainers порекомендовала бы из опыта таких вариантов использования.

Kevin Wittek 08.04.2022 19:02

Другие вопросы по теме