Spring JDBC 在运行 SQL 脚本时无法处理 DECLARE 语句

Posted

技术标签:

【中文标题】Spring JDBC 在运行 SQL 脚本时无法处理 DECLARE 语句【英文标题】:Spring JDBC cannot handle DECLARE statement when running SQL Script 【发布时间】:2020-02-12 12:11:24 【问题描述】:

我有一个带有 Oracle SQL 数据库的 Spring CRUD 应用程序。

我有几个集成测试并使用@Sql 注释运行.sql 文件,这些文件在运行测试之前将数据库置于所需状态。到目前为止,我的任何 .sql 脚本都没有问题,尽管它们都是非常简单的 INSERT 和 DROP 语句。

我现在正在尝试设置一个场景,其中数据库在特定表中保存数千条记录。我不关心内容,只关心记录的数量。

为了复制这个场景,我编写了以下 SQL 脚本:

DECLARE
    id integer := 1;
BEGIN
    WHILE id <= 3000
        LOOP
            INSERT INTO USER (ID, FIRST_NAME, LAST_NAME, AGE)
            VALUES (id, 'John', 'Smith', 32);
            id := id + 1;
        END LOOP;
END;

我已经使用 IntelliJ 运行了这个脚本,它按预期工作。

但是,当我将脚本放入@Sql 注释时,我遇到了以下错误:

org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of class path resource [sql/setUp.sql]: DECLARE id integer := 1; nested exception is java.sql.SQLException: ORA-06550: line 1, column 23:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:

   * & = - + ; < / > at in is mod remainder not rem
   <an exponent (**)> <> or != or ~= >= <= <> and or like like2
   like4 likec between || multiset member submultiset

我不知道如何解决;我遇到过类似的问题,解决方案只是缺少分号,但这里似乎并非如此。 更令人费解的是,我尝试通过@Sql 注解运行以下脚本:

BEGIN
    INSERT INTO USER (ID, FIRST_NAME, LAST_NAME, AGE)
    VALUES (1, 'John', 'Smith', 32);
END;

这产生了以下错误,这更没有意义:

org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #1 of class path resource [sql/test.sql]: BEGIN INSERT INTO USER (ID, FIRST_NAME, LAST_NAME, AGE) VALUES (1, 'John', 'Smith', 32); nested exception is java.sql.SQLException: ORA-06550: line 1, column 87:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:

   ;

最值得注意的是删除 BEGIN 和 END 语句(只留下 INSERT INTO 语句)是可行的。

那么 Spring JDBC 是否根本不支持 BEGIN/END 块?我不知道的预测试 @Sql 注释是否有限制?还是我错过了一些非常明显的事情?

【问题讨论】:

请提供minimal reproducible example。问题似乎是,无论您在做什么都是在; 上拆分以发送单个语句(因为 JDBC 本身仅支持单个语句执行)。要执行这样的脚本,通常需要以某种方式切换或禁用语句终止符才能发送整个内容。这意味着您可能需要微调@Sql@SqlConfig(例如separator 的值)。 @MarkRotteveel:Oracle JDBC 驱动程序确实支持运行匿名 PL/SQL 块,因为它实际上是一条语句。可能是 Spring 试图将 看起来 的多个语句分开 @a_horse_with_no_name 是的,@Sql 的 default 将在 ; 上拆分。所以就 Spring 而言,在遇到每个 ; 之后,都会启动一个新语句。 不是答案,因为我不确定它是否正确,但您可以尝试在 END; 语句之后添加另一行,第一列中只有一个 / 字符。 @MarkRotteveel 感谢您的建议,我找到了解决方案。非常感谢! 【参考方案1】:

感谢@MarkRotteveel 的建议,我找到了解决这个问题的方法。

JDBC 确实在运行脚本的每一行,就好像它是一个单独的语句,这与语句块不兼容。这是因为 separator 默认设置为 ; 符号,导致 JDBC 将每个以 ; 结尾的代码块视为单独的脚本。我将此分隔符设置为 EOF 符号,这导致 JDBC 将整个文件视为单个脚本。

我的原始测试代码如下所示:

@Sql(scripts = "sql/cleanup.sql", "sql/setUp.sql")
public void testWithThousandsOfRecords() 
  // do tests

我的测试代码现在看起来像这样:

@Sql(scripts = "sql/cleanup.sql")
@Sql(scripts = "sql/setUp.sql", config = @SqlConfig(separator = ScriptUtils.EOF_STATEMENT_SEPARATOR))
public void testWithThousandsOfRecords() 
  // do tests

请注意,我必须将我的清理和设置脚本分开,因为更改分隔符可能会破坏一些以前工作的脚本。

【讨论】:

如果您需要在脚本中有多个语句,请考虑使用自定义语句终止符(例如# 而不是;),而不是使用EOF_STATEMENT_SEPARATOR。然后您可以使用# 结束语句,同时您可以继续在PL/SQL 块中使用; 在这种情况下你会怎么做?

以上是关于Spring JDBC 在运行 SQL 脚本时无法处理 DECLARE 语句的主要内容,如果未能解决你的问题,请参考以下文章

Spring:HSQL-无法执行数据库脚本

Sql Server JDBC无法在wildfly AS上运行

Spring JDBC 无法加载 JDBC 驱动程序类 [oracle.jdbc.driver.OracleDriver]

使用带有 JDBC 的 MySQL 运行 .sql 脚本

如何使用 Spring 管理具有不同 jdbc(或休眠?)的多个“运行时注意到”数据库连接?

我可以从 JDBC 连接运行“源”命令(SQL 脚本)吗?