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

Posted

技术标签:

【中文标题】无法在 schema.sql 中为 Spring Boot 应用程序使用“DROP TABLE IF EXISTS”【英文标题】:Unable to use "DROP TABLE IF EXISTS" in schema.sql for a Spring Boot application 【发布时间】:2016-08-10 22:03:11 【问题描述】:

我需要有关 DROP/CREATE 在我的 schema.sql 中的表的帮助

设置:

Oracle XE Spring Boot v1.4.0 Java 1.8

当我在 schema.sql 中有以下条目时:

DROP TABLE table_a;

CREATE TABLE table_a
(
    id                       VARCHAR(5) PRIMARY KEY,
    name                     VARCHAR(100));

我得到了异常

DROP TABLE table_a; nested exception is java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist

当我查找有关如何在 Oracle 中执行 DROP TABLE IF EXISTS 的帮助时,我得到的最佳答案如下(在 SQLDeveloper 中工作):

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;

  EXECUTE IMMEDIATE 'CREATE TABLE table_a
  (
    id               VARCHAR(5) PRIMARY KEY,
    name             VARCHAR(100)
  )';
END;

但是,上面的代码抛出了以下异常:

2016-08-10 14:55:36.232 INFO 9032 --- [main] o.s.jdbc.datasource.init.ScriptUtils:从 URL [file:/C:/projects/project_a/target/classes/ 执行 SQL 脚本架构.sql] 2016-08-10 14:55:36.286 WARN 9032 --- [main] o.s.w.c.s.GenericWebApplicationContext:上下文初始化期间遇到异常-取消刷新尝试:org.springframework.beans.factory.BeanCreationException:创建名为“org.springframework”的bean时出错.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration':自动装配依赖注入失败;嵌套异常是 org.springframework.beans.factory.BeanCreationException:无法自动装配字段:私有 javax.sql.DataSource org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.dataSource;嵌套异常是 org.springframework.beans.factory.BeanCreationException:在类路径资源 [org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration$NonEmbeddedConfiguration.class] 中定义名称为“dataSource”的 bean 创建错误:bean 初始化失败;嵌套异常是 org.springframework.beans.factory.BeanCreationException:创建名为“dataSourceInitializer”的 bean 时出错:调用 init 方法失败;嵌套异常是 org.springframework.jdbc.datasource.init.ScriptStatementFailedException: 未能执行 SQL 脚本语句 #1 of URL [file:/C:/projects/project_a/target/classes/schema.sql]: BEGIN EXECUTE IMMEDIATE 'DROP表表_a';嵌套异常是 java.sql.SQLException: ORA-06550: 第 1 行,第 44 列: PLS-00103:在预期以下情况之一时遇到符号“文件结尾”

& = - + ; > at in 是 mod 余数不是 rem 返回 返回 或 != 或 ~= >= 和 or like like2 like4 likec 之间进入使用 ||多组批量 成员子集

有人有更优雅的方式来处理 Spring Boot 中 Oracle 表的 DROP/CREATE 吗?

【问题讨论】:

不确定这是否完全符合您的要求;表的创建在异常处理程序中 - 所以如果表确实存在,它将被删除而不是重新创建。无论如何,您的框架将前两行(直到第一个分号)视为独立语句 - 显然,它不知道整个事情是一个 PL/SQL 块。你能改变语句分隔符吗? create or replace table in oracle pl/sql的可能重复 @mathguy - 问题不在于如何在 PL/SQL 中删除和创建表(这里似乎来自模式创建脚本,因此在运行时创建表警告并不真正适用),但是如何从 Spring Boot 调用 PL/SQL 块? 我用一个长句替换-> "BEGIN EXECUTE IMMEDIATE 'DROP TABLE table_a'; 其他情况下除外 SQLCODE != -942 THEN RAISE; END IF; EXECUTE IMMEDIATE 'CREATE TABLE table_a (id VARCHAR(5) 主键,名称 VARCHAR(100))'; END;"。但是,我仍然得到相同的异常。 在这种情况下不需要使用动态 SQL 运行 CREATE TABLE,因为它是静态的。只用异常处理程序包装 DROP TABLE。 【参考方案1】:

您尚未显示您的 Java 代码,但从堆栈跟踪看来您正在调用 ScriptUtil's executeSqlScript() method,它使用了默认的分号语句分隔符。

它没有将 PL/SQL 块识别为单个单元,而是尝试将第一个分号之前的所有内容作为独立的 SQL 语句运行 - 这是无效的,并会导致您看到的错误。

您可以使用 the version of executeSqlScript() 覆盖默认值并改用 /

public static void executeSqlScript(Connection connection,
                                    EncodedResource resource,
                                    boolean continueOnError,
                                    boolean ignoreFailedDrops,
                                    String commentPrefix,
                                    String separator,
                                    String blockCommentStartDelimiter,
                                    String blockCommentEndDelimiter)
                             throws ScriptException

separator - 脚本语句分隔符;默认为“;”如果未指定,则作为最后的手段回退到“\n”;可以设置为“^^^ END OF SCRIPT ^^^”来表示脚本包含一个没有分隔符的语句

这意味着脚本中的所有 SQL 语句也必须使用/ 分隔符而不是分号:

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;
END;
/

CREATE TABLE table_a
  (
    id               VARCHAR(5) PRIMARY KEY,
    name             VARCHAR(100)
  )
/

...

正如 cmets 中所指出的,您的原始块并不完全正确;并且 create 不需要通过 PL/SQL 完成,即使 drop 需要这样做。

但该方法也有一个 ignoreFailedDrops 标志,它似乎完全符合您的要求(但我无法对其进行测试):

ignoreFailedDrops - 如果 DROP 语句出现特定错误,是否继续

如果您使用该版本并为该标志传递 true,则您不需要围绕 drop 的 PL/SQL 包装器;您可以保留分号分隔符并恢复为:

DROP TABLE table_a;

CREATE TABLE table_a
(
    id                       VARCHAR(5) PRIMARY KEY,
    name                     VARCHAR(100)
);

...

如果您的架构脚本包含任何其他 PL/SQL - 触发器、包等 - 那么您仍然需要切换到使用斜线分隔符(或您选择的任何其他分隔符;斜线是传统的) .

【讨论】:

感谢您的详尽建议。我没有看到让 Spring-Boot 使用上述版本的 executeSqlScript() 方法的快速方法。所以,我正在使用 Gary Myers 提供的以下建议。当我有时间时,我将研究如何使用上述方法使其工作。【参考方案2】:

创建存储过程

create or replace procedure recreate_table 
  (i_table_name in varchar2, i_create_stmt in varchar2) 
is
BEGIN
  BEGIN
    EXECUTE IMMEDIATE 'DROP TABLE '||upper(i_table_name);
  EXCEPTION
    WHEN OTHERS THEN
      IF SQLCODE != -942 THEN
        RAISE;
      END IF;
  END;
  EXECUTE IMMEDIATE i_create_stmt;
END;

那么你的schema.sql就可以使用SQL语句了:

call recreate_table('TABLE_A','CREATE TABLE TABLE_A (ID NUMBER, VAL VARCHAR2(10))');

而不是包括 PL/SQL

【讨论】:

这是唯一有效的方法。在 Spring-Boot 中,schema.sql 不采用 PL/SQL。【参考方案3】:

Oracle 鼓励您使用全局临时表,而不是删除和重新创建表。全局临时表的优点是您不必为了重新创建表而执行删除表的例程。

现在,关于全局临时表的问题在于,其中的数据仅对创建它的会话可见,并且当 A) 事务提交或 B) 会话断开连接时,数据会自动删除 - 你在创建表时可以选择何时删除数据,但数据不会长时间保留,也不会对连接到数据库的每个人可见。它适用于“便签本”表,其中应用程序需要将数据临时放入表中,在给定会话中使用它,然后将其删除。如果这符合您的预期用途,这将是一个不错的选择。

要将您的表创建为全局临时表,您需要指定以下内容:

CREATE GLOBAL TEMPORARY TABLE table_a
  (id                       VARCHAR(5) PRIMARY KEY,
   name                     VARCHAR(100))
  ON COMMIT PRESERVE ROWS;

(作为旁注——你应该养成在 Oracle 中使用 VARCHAR2 而不是 VARCHAR 的习惯。VARCHAR 是 Oracle 不正确支持的 ANSI 类型。在 Oracle 中,VARCHAR 目前是 VARCHAR2 的同义词,但谣言是某天 Oracle 将更改 VARCHAR 使其完全符合 ANSI,如果您在表中使用它,您的数据库的行为可能会悄悄地改变而不会发出警告。所以这是您的警告 :-)。

祝你好运。

【讨论】:

正在运行的schema.sql 脚本建议这是初始模式创建,在这种情况下保护性下降并非不合理。当然,您必须希望该脚本不会在某一天意外重新运行……但如果是这种情况,则不确定 GTT 是否相关。 好消息 -> 上面的脚本有效。但是,在应用程序终止后,我预计这些表会被删除。他们留下了。其次,当我再次运行应用程序时,我希望它们会被覆盖。但是,我收到异常“ORA-00955:名称已被现有对象使用” 全局临时表的好处是你只需要创建一次它就可以继续存在。您不必一遍又一遍地删除和重新创建表。 @BobJarvis,我明白你的意思。但是,我们在这里所做的是在每次构建 mvn clean package 时为单元测试设置数据库。因此,我们需要一个脚本来删除所有内容并重新创建表并随后填充它们。【参考方案4】:

这是旧线程,但我在最新的 Spring Boot - 1.5.4 中偶然发现了同样的问题,我想我已经找到了答案(也感谢上面的 Alex Poole)。 Spring Boot 默认使用属性

spring.datasource.separator=;

所以你可以看到';'用作 SQL 命令的分隔符。因此,在尝试将 PL/SQL 过程放入“schema.sql”的过程中,Oracle DB 仅接收第一个 SQL 命令并尝试将其写入 DB。所以有代码:

BEGIN
  EXECUTE IMMEDIATE 'DROP TABLE table_a';
  EXCEPTION
  WHEN OTHERS THEN
  IF SQLCODE != -942 THEN
    RAISE;
  END IF;
END;

仅在 DB 中 BEGIN 和 EXECUTE IMMEDIATE 'DROP TABLE table_a';正在存储 - 你可以在例如看到。 SQL 开发人员。将分隔符更改为例如。 ';;'帮助 - 并在 SQL 代码中使用它而不是 ';'。我不建议使用 '/' 作为分隔符,因为这是用于创建多行 SQL-cmets 的符号,因此当有人在 SQL 文件中使用它们时可能会导致问题。

【讨论】:

【参考方案5】:

我认为这确实是一个 TSQL 问题。您可以使用 tsql 存在语句来查看系统表并查看您的对象是否存在。

【讨论】:

这是一个 Oracle 问题,因此与 TSQL 无关,它是 Microsoft SQL Server 的一部分。

以上是关于无法在 schema.sql 中为 Spring Boot 应用程序使用“DROP TABLE IF EXISTS”的主要内容,如果未能解决你的问题,请参考以下文章

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

为啥 Spring Boot 2.0 应用程序不运行 schema.sql?

mvn spring-boot:run 不会在资源中执行 schema.sql

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

值为“类路径资源 [schema-mysql.sql]”的​​属性 spring.datasource.schema 无效:指定的资源不存在

无法在 Spring Security 中为 oauth/token 端点启用 CORS