【问题标题】:Issue with executing procedure in spring boot schema.sql file在 spring boot schema.sql 文件中执行过程的问题
【发布时间】: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 工作台中执行此过程并获得成功的结果。

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

【问题讨论】:

标签: java mysql spring spring-boot stored-procedures


【解决方案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,它简化了事情,所以不需要对所有内容进行底层实现
        • 在搜索并尝试了各种绝对有效的“提示”后,以下资源帮助我找到了那些确实有效的解决方案:

        解决方案 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找到

        【讨论】:

          猜你喜欢
          • 2021-09-05
          • 2015-03-11
          • 2021-10-06
          • 2017-10-16
          • 2020-01-24
          • 1970-01-01
          • 2018-08-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多