【问题标题】:"x is a procedure, use "call"" when I am already using call"x 是一个过程,当我已经在使用 call 时使用 "call""
【发布时间】:2020-12-02 22:13:09
【问题描述】:

我正在使用 Postgres 12 并编写了以下程序:

CREATE OR REPLACE PROCEDURE reduceStock(id INTEGER, soldQuantity INTEGER)
    LANGUAGE plpgsql AS
    $$
    BEGIN
    UPDATE inventory SET ProductStockAmount = ProductStockAmount - soldQuantity WHERE ProductID = id;     
    END;
    $$;

如果我在命令行上打开 psql 并运行 call reduceStock(1,1);,它会完美运行

但是,从我的 Java 程序中调用它如下:

CallableStatement stmt = conn.prepareCall("{call reduceStock(?, ?)}");
stmt.setInt(1, productID);
stmt.setInt(2, quantity);
stmt.execute();

给我以下错误:

我的尝试

  • 从 psql 客户端运行 call reduceStock(1,1); - 完美运行
  • 删除数据库并重新开始查看是否缓存了某些旧定义 - 不起作用
  • 不同的大小写,call 的间距

任何想法将不胜感激

【问题讨论】:

  • 您使用的是最新版本的 JDBC 驱动程序吗?

标签: java postgresql jdbc psql procedure


【解决方案1】:

您需要删除花括号,这是调用过程的 JDBC 转义。但是因为 Postgres 有它自己的 call 命令,所以不需要它们(并且会与 JDBC 转义冲突)。

CallableStatement stmt = conn.prepareCall("call reducestock(?, ?)");

【讨论】:

  • 对我来说这听起来像是 PostgreSQL 驱动程序中的一个错误,它应该处理转义并将其转换为数据库特定的语法。
  • 我认为这是由 Postgres 中的一些技术限制引起的。我虽然在邮件列表上对此进行了讨论,但我现在找不到。
  • @MarkRotteveel 有一种方法可以配置驱动程序以正确处理这种情况 - 请参阅my answer
  • @jannis:我认为在我写答案时 escapeSyntaxCallMode 选项不存在
  • The commit introducing this setting 来自 2019 年 11 月,如果我没记错的话,于 2019 年 12 月发布 (42.2.9)。但我承认它并不容易找到。事实上,我偶然发现了这个功能。您的帖子将我的调查推向了正确的方向(JDBC 驱动程序)。
【解决方案2】:

过程调用 ({call reduceStock(?, ?)}) 周围的花括号表示这不是原生 SQL,而是 JDBC 语法。你可以在这里阅读更多信息:Why do JDBC calls to stored procedures wrap the call in curly brackets?

因此,像这样的调用仍然必须由 JDBC 驱动程序转换为原生 SQL。碰巧,默认情况下,Postgres 驱动程序将此类语句视为函数调用并将它们转换为SELECT reduceStock(?, ?) SQL 查询。这不是在 Postgres 中调用存储过程的方式。在 Postgres 中,存储过程调用 SQL 是 call reduceStock(?, ?)

使其工作的一种方法是,如@a_horse_with_no_namehis answer 中所写,删除花括号。这使得该语句成为一个本地调用,并且因为它是一个有效的 Postgres SQL,所以它会起作用。缺点是它减少了跨平台,因为它不适用于不支持call procname() 语法的数据库。例如,这不适用于 Oracle,因此如果您必须支持多个 JDBC 驱动程序,这是不太可取的方法。

更好的解决方法是提示 Postgres JDBC 驱动程序将此语法视为存储过程调用而不是函数调用,并相应地将其转换为 SQL。为此,Postgres 驱动程序公开了一个escapeSyntaxCallMode 配置属性(也请查看EscapeSyntaxCallMode enum):

指定驱动程序如何将 JDBC 转义调用语法转换为底层 SQL,以调用过程或函数。 (backend >= 11) 在 escapeSyntaxCallMode=select 模式(默认)中,驱动程序总是使用 SELECT 语句(只允许函数调用)。在 escapeSyntaxCallMode=callIfNoReturn 模式下,如果没有指定返回参数,驱动程序使用 CALL 语句(允许过程调用),否则驱动程序使用 SELECT 语句。在 escapeSyntaxCallMode=call 模式下,驱动程序始终使用 CALL 语句(仅允许过程调用)。

如您所见,默认情况下,所有{call something()} 语句都被视为函数调用,并且始终转换为 SELECT。将escapeSyntaxCallMode 设置为call 将使驱动程序将它们转换为call SQL 语句。 callIfNoReturn 选项对于大多数用例来说似乎是最合理的,因为如果没有指定返回参数,它将把 JDBC 调用转换为存储过程调用,否则作为函数调用。

您可以在 Postgres 文档 (Chapter 6. Calling Stored Functions and Procedures) 中找到使用此设置的示例:

// set up a connection
String url = "jdbc:postgresql://localhost/test"; 
Properties props = new Properties(); 
// ... other properties ... 
// Ensure EscapeSyntaxCallmode property set to support procedures if no return value
props.setProperty("escapeSyntaxCallMode", "callIfNoReturn");
Connection con = DriverManager.getConnection(url, props);

// Setup procedure to call.
Statement stmt = con.createStatement();
stmt.execute("CREATE TEMP TABLE temp_val ( some_val bigint )");
stmt.execute("CREATE OR REPLACE PROCEDURE commitproc(a INOUT bigint) AS '"
    + " BEGIN "
    + " INSERT INTO temp_val values(a); "
    + " COMMIT; "
    + " END;' LANGUAGE plpgsql");
stmt.close();

// As of v11, we must be outside a transaction for procedures with transactions to work.
con.setAutoCommit(true);

// Procedure call with transaction
CallableStatement proc = con.prepareCall("{call commitproc( ? )}");
proc.setInt(1, 100);
proc.execute(); proc.close();>

-- https://jdbc.postgresql.org/documentation/head/callproc.html#call-procedure-example

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-18
    • 1970-01-01
    • 1970-01-01
    • 2017-02-20
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多