是否可以创建一个准备好的语句并稍后在 postgres 下使用 Java 重用它?

Is it possible to create a prepared statement and reuse it later with Java under postgres?

我正在尝试用 postgres 测试一些东西。我想知道是否可以创建定义一个 PreparedStatement,例如

String statement = "Insert into table_one values (?)";
PreparedStatement insert = con.prepareStatement(statement);
insert.execute()
// tell postgres to create this Prepared statement without 

当我尝试时,我收到一条错误消息:
SQLState: 22023 No value specified for parameter 1

此外,如果我改为执行以下操作

    PreparedStatement insert = con.prepareStatement(statement);
    insert.setInt(1, 10); //insert into table_one values (10);
    insert.execute()
    .... //commit other transactions to postgres
    insert.setInt(1, 20);
    insert.execute();

postgres 会“记住”/已将以上内容注册为准备好的语句吗(我不是在谈论缓存实际语句)?

正如您的第二个片段所示,是的,您可以做到。

您可以创建一个准备语句,然后等待 ages 和 ages,然后设置每个 'parameter'(调用例如 .setInt(1, ...),对于您传递的 SQL 字符串中的每个问号),并调用其中一种执行方法,然后...再等一段时间,然后再执行一次,次数不限。

只要连接存在,准备好的语句就会存在,或者直到你关闭它,你应该这样做。真正的问题在于:关闭它们。 您必须明确关闭它们。它们就像文件和网络套接字那样。

如果你搞砸了,你的应用不会立即崩溃,也不会出现任何测试失败。但是,连接是永久性的 'damaged',因为它只能容纳有限数量的准备好的语句。随着你不断地制造更多并且永远不会关闭它们,最终你 运行 出局,并且会发生奇怪的 SQL 异常。您的应用程序是一颗定时炸弹。这是最严重的 class 错误:难以测试,不可避免地 会在您的应用程序变得繁忙时爆炸。当 bossman 开始询问法律团队他们是否可以起诉你的严重疏忽时,那是你离开的时候:Oof,也许我不应该玩弄那些导致无法轻易测试、难以发现并且会导致错误的代码样式只有在事情很忙的时候才会引起问题。

所以,(严格地)遵守规程来避免这样的事情:

  1. 任何创建的资源(通过 new SomeResource(),或通过 crystal 明确表明它是创建者的方法,例如 Files.newInputStream,必须由相同的代码,,该代码又必须是可关闭的资源。
  2. 加强#1,除非你自己是一个可关闭的实体,否则你只能在try-with块中打开资源。

换句话说,要么你这样做:

try (PreparedStatement ps = ....) {
    // use ps here, as often as you want....
} // ps is closed here

或者你这样做:

class WhateverTool implements AutoClosable {
    private PreparedStatement ps; // long-lived

    @Override public void close() throws SQLException {
        if (ps != null) ps.close();
    }
}

并且您与此 class 的 交互本身采用以下形式:

try (WhateverTool t = new WhateverTool()) {
    // do whatever you want here...
} // but this is where it ends

并且您的代码库中不允许使用其他选项与资源进行交互。

Java 不强制执行此操作,但如果您不遵守这些规则,您肯定会遇到麻烦。

当你在 PostgreSQL JDBC 驱动程序中使用 java.sql.PreparedStatement 时,它首先不会在数据库服务器上创建一个真正的准备好的语句,而只是构造一个简单的 SQL语句发送到数据库服务器。只有在第六次执行时,它才会认为在服务器上创建一个 命名准备语句 是值得的,它会在以后的执行中重复使用。

您可以使用 prepareThreshold 连接 属性 来影响行为,请参阅 the documentation。因此,要使您的第二个示例使用服务器准备语句,您必须将阈值降低到 0。只有当您知道将重用所有准备语句时,这才有用;考虑到准备好的语句通常用于其他目的,例如避免 SQL 注入问题。

在数据库服务器上有一个类似的功能:执行准备好的语句的前五次,PostgreSQL 为它计算一个自定义计划。只有在第六次执行时,它才会考虑切换到 generic plan,这样您就可以避免从此以后的计划开销。从 v12 开始,这会受到 PostgreSQL 参数 plan_cache_mode 的影响。

因此,使用默认设置,需要执行 10 次 java.sql.PreparedStatement 才能看到避免计划成本带来的性能提升。