在 spring boot schema.sql 文件中执行过程的问题

Posted

技术标签:

【中文标题】在 spring boot schema.sql 文件中执行过程的问题【英文标题】:Issue with executing procedure in spring boot schema.sql file 【发布时间】:2015-12-30 11:00:57 【问题描述】:

我在我的 Spring Boot 应用程序中使用schema.sql 文件到CREATE/DROP 表,它工作正常。

但是当我添加了更改表的过程时:

DELIMITER $$
CREATE PROCEDURE Alter_Table()
BEGIN
 IF NOT EXISTS( SELECT NULL
            FROM INFORMATION_SCHEMA.COLUMNS
           WHERE table_name = 'test_table'
             AND table_schema = 'test'
             AND column_name = 'cc_test_id')  THEN

  alter table test_table add cc_test_id VARCHAR(128) NOT NULL;

END IF;
END $$

call Alter_Table;

我收到了:

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException exception.

但是,在 MySQL 工作台中执行此过程并获得成功的结果。

那么,有谁知道这个问题的原因,请告诉我?

【问题讨论】:

看这里如何创建你不需要的程序DELIMITER***.com/questions/31769551 【参考方案1】:

这是我发现效果很好的解决方案,尽管它并不理想,因为您必须更改 SQL 脚本。

在您的 application.properties 文件中更改 DataSource 分隔符属性:

spring.datasource.separator=^;

然后更新您的schema.sql 文件,如下所示:

CREATE PROCEDURE Alter_Table()
BEGIN
 IF NOT EXISTS( SELECT NULL
            FROM INFORMATION_SCHEMA.COLUMNS
           WHERE table_name = 'test_table'
             AND table_schema = 'test'
             AND column_name = 'cc_test_id')  THEN

  alter table test_table add cc_test_id VARCHAR(128) NOT NULL;

END IF;
END ^;

call Alter_Table ^;

DELIMITER 命令仅适用于 MySQL CLI 客户端和 Workbench,不适用于 Spring Boot 数据库初始化。删除 DELIMITER 命令后,Spring Boot 仍然会抛出异常,因为它无法理解存储过程中的 ; 字符不是单独的语句,因此您必须更改数据源分隔符属性作为解决方法。

【讨论】:

【参考方案2】:

解决方法如下:

将您的spring.datasource.separator 属性设置为^^^ END OF SCRIPT ^^^,Spring Boot 会将您的整个 schema.sql 脚本作为一条语句执行。

原因如下:

Spring Boot 将您的 schema.sql 脚本拆分为语句,然后将每个语句单独发送到数据库以执行。默认情况下,脚本以分号分隔,因为这是spring.datasource.separator 属性(per the documentation) 的默认值。这会导致您的 schema.sql 被拆分,执行的第一条语句如下:

DELIMITER $$
CREATE PROCEDURE Alter_Table()
BEGIN
 IF NOT EXISTS( SELECT NULL
            FROM INFORMATION_SCHEMA.COLUMNS
           WHERE table_name = 'test_table'
             AND table_schema = 'test'
             AND column_name = 'cc_test_id')  THEN

  alter table test_table add cc_test_id VARCHAR(128) NOT NULL

这是无效的 SQL,因为美元报价从未在语句中终止。

org.springframework.jdbc.datasource.init.ScriptUtils.EOF_STATEMENT_SEPARATOR 的 javadoc 很好地解释了我建议的解决方案的工作原理:

文件结束 (EOF) SQL 语句分隔符:“^^^ END OF SCRIPT ^^^”。

此值可以作为分隔符提供给 executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String) 以表示 SQL 脚本包含单个语句(可能跨越多行)而没有显式语句分隔器。请注意,这样的脚本实际上不应包含此值;它只是一个虚拟语句分隔符。

【讨论】:

【参考方案3】:

使用适当的分隔符 EOF_STATEMENT_SEPARATOR 自定义数据库填充器可解决此问题:

@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) 
    var resource = new ResourceDatabasePopulator(new ClassPathResource("schema.sql"));
    resource.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(resource);

【讨论】:

【参考方案4】:

上述答案在 2022 年对我来说都不起作用,但它们为我提供了一个焦点来提出我自己的工作解决方案。我在类似问题下发布了我的答案,但为了完整起见,可以为您节省一些额外的点击次数,我也在此处提供。

我也遇到过这个问题,现在我找到了解决方案。 其实是3!因此,您可以选择适合您需求的任何一种。但它的关键是正确格式化的String,它代表创建SP 的DB 脚本。 我还没有弄清楚如何在一个 SQL 脚本/字符串中结合删除和创建 SP,但无论如何我对这些解决方案很满意。

仅供参考:

不需要在类或方法级别上进行特殊注释或设置 我正在使用Spring FW,它简化了事情,所以不需要对所有内容进行底层实现 在搜索并尝试了各种绝对有效的“提示”后,以下资源帮助我找到了那些确实有效的解决方案: Execute SQL file from Spring JDBC Template *** 上的类似问题 Spring and MySQL stored procedure,我实际上已经在那里发布了我的答案 但最有用的是官方 Oracle tutorial 上的 JavaSE 和 JDBC 和其他Java/JDBC tutorial 在这里您可以看到格式化sql Java String 最重要的一点

解决方案 1:使用 JdbcTemplate 和 Java String

在格式良好的String sql 脚本中包含尾随空格非常重要

        String sqlSP = "CREATE PROCEDURE test_stored_proc (IN pInstanceId varchar(255)) " +
                "BEGIN " +
                    "SELECT * FROM vw_subscriptions WHERE instanceId = pInstanceId; " +
                "END";

        jdbcTemplate.execute(sqlSP);

解决方案 2:使用ResourceDatabasePopulator & ClassPathResource 在文件中加载 SQL 脚本

        ClassPathResource resource = new ClassPathResource("/data/create-sp_TEST_STORED_PROC.sql");
        jdbcTemplate.execute("DROP procedure IF EXISTS `test_stored_proc`;");

        ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(resource);
        databasePopulator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
        databasePopulator.execute(testSetupDataSource);

解决方案 3:使用ScriptUtils & ClassPathResource 在文件中加载 SQL 脚本

        ClassPathResource resource = new ClassPathResource("/data/create-sp_TEST_STORED_PROC.sql");
        jdbcTemplate.execute("DROP procedure IF EXISTS `test_stored_proc`;");

        ScriptUtils.executeSqlScript(Objects.requireNonNull(testSetupDataSource).getConnection(), new EncodedResource(resource),
                false, false,
                ScriptUtils.DEFAULT_COMMENT_PREFIX, ScriptUtils.EOF_STATEMENT_SEPARATOR,
                ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER, ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);

create-sp_TEST_STORED_PROC.sql 脚本的内容

对于解决方案 #1 和 #2,适用与第一个解决方案相同的规则:

空白字符的格式和存在非常重要,尤其是每行末尾的额外尾随空格。

所以下面的代码是由我的vim设置存档的,代表空白字符

CREATE·PROCEDURE·test_stored_proc·(IN·pInstanceId·varchar(255))~¬
BEGIN~¬
–→SELECT·*·FROM·vw_subscriptions·WHERE·instanceId·=·pInstanceId;~¬
END;~¬

我相信它在内部表示为一行字符串: CREATE PROCEDURE test_stored_proc (IN pInstanceId varchar(255)) BEGIN SELECT * FROM vw_subscriptions WHERE instanceId = pInstanceId; END

几乎完整的源代码可以在我的GitHub找到

【讨论】:

以上是关于在 spring boot schema.sql 文件中执行过程的问题的主要内容,如果未能解决你的问题,请参考以下文章

在 spring boot schema.sql 文件中执行过程的问题

spring boot2.0后配置自动执行schema.sql无效

无法在 schema.sql 中为 Spring Boot 应用程序使用“DROP TABLE IF EXISTS”

Spring Boot 无法运行依赖于 spring-cloud-starter-config 的 schema.sql

spring boot 项目在启动时执行指定sql文件

内存数据库H2中的Spring Boot在初始化时不会从文件中加载数据