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

huangapple go评论79阅读模式
英文:

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();
// 告诉Postgres创建这个预处理语句但不要赋值

当我尝试时,我收到了一个错误,错误消息如下:

No value specified for parameter 1```

此外,如果我尝试以下操作:

```java
String statement = "Insert into table_one values (?)";
PreparedStatement insert = con.prepareStatement(statement);
insert.setInt(1, 10); // 插入到table_one的值为(10)
insert.execute();
// ... 提交其他事务到Postgres
insert.setInt(1, 20);
insert.execute();

Postgres会“记住”/已注册上述内容作为一个预处理语句(我不是在谈论缓存实际语句)吗?

英文:

I am trying to test a few things out with postgres. I want to know if it's possible to create define a PreparedStatement such as

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

When I tried, I received an error stating: <br>
SQLState: 22023
No value specified for parameter 1

Additionally, is if I were to do the following instead

    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();

Will postgres "remember"/has registered the above as a prepared statement (I'm not talking about caching the actual statement)?

答案1

得分: 4

使用PostgreSQL JDBC驱动程序时,如果您使用java.sql.PreparedStatement,初始情况下不会在数据库服务器上创建真正的预处理语句,而只会构造一个简单的SQL语句发送到数据库服务器。直到第六次执行时,它才会认为值得在服务器上创建一个命名预处理语句,并在以后的执行中重复使用该语句。

您可以使用prepareThreshold连接属性来影响此行为,请参阅文档。因此,要使第二个示例使用服务器预处理语句,您需要将阈值降低为0。只有在您知道将要重用所有预处理语句时,才会有所用;请考虑预处理语句通常用于避免SQL注入等问题。

在数据库服务器上,存在类似的功能:首次执行准备语句的五次操作,PostgreSQL会为其计算一个自定义计划。直到第六次执行,它才会考虑切换到通用计划,以便您可以避免以后的计划开销。从v12开始,可以通过PostgreSQL参数plan_cache_mode来影响此行为。

因此,在默认设置下,需要执行十次java.sql.PreparedStatement才能看到通过避免计划成本而带来的性能提升。

英文:

When you use a java.sql.PreparedStatement with the PostgreSQL JDBC driver, it will at first not create a real prepared statement on the database server, but just construct a simple SQL statement to send to the database server. Only at the sixth execution it will think that it is worth the effort to create a named prepared statement on the server that it reuses for future executions.

You can use the prepareThreshold connection property to influence the behavior, see the documentation. So to make your second example use a server prepared statement, you would have to lower the threshold to 0. This is useful only if you know you will reuse all your prepared statements; consider that prepared statements are often used for other purposes like avoiding SQL injection problems.

On the database server there is a similar functionality: the first five times a prepared statement is executed, PostgreSQL computes a custom plan for it. Only at the sixth execution it will consider switching to a generic plan instead, so that you can avoid the overhead of planning from then on. This can be influenced with the PostgreSQL parameter plan_cache_mode from v12 on.

So, with the default settings, it will take ten executions of a java.sql.PreparedStatement before you see a performance increase from avoiding planning costs.

答案2

得分: 1

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

您可以创建一个 preparedstatement,然后等待很长时间,然后设置每个 'parameter'(例如调用 .setInt(1, ...),为您传递的 SQL 字符串中的每个问号设置值),然后调用其中一个 execute 方法,然后... 再等待一段时间,尽您喜欢的次数都可以。

一个 preparedstatement 的生存周期与连接一样长,或者直到您关闭它,这是您应该做的。真正的关键在于关闭它们。您必须明确关闭它们。它们就像文件和网络套接字一样。

如果您搞砸了这一点,您的应用程序不会立即崩溃,也不会出现任何测试失败。但是,连接将永久性地 '损坏',因为它只能容纳有限数量的 prepared statement。当您不断创建更多 prepared statement 并且从不关闭它们时,最终会用尽它们,然后会发生奇怪的 SQL 异常。您的应用程序就像一颗定时炸弹。这是最糟糕的错误类别:难以测试,难以发现,并且只会在繁忙时出现问题。当老板开始询问法律团队是否可以起诉您的重大过失时,那就是您认识到:哎呀,也许我不应该玩弄那些难以测试、难以发现的代码风格,它们只会在事情变得繁忙时引发问题。

因此,要(严格)遵循协议以避免这些问题:

  1. 任何创建的资源(通过 new SomeResource() 或者通过非常清晰表明它是一个创建者的方法,例如 Files.newInputStream)都必须由相同的代码关闭,或者该代码必须本身是可关闭的资源。
  2. 更详细地说,除非您本身是一个可关闭的实体,否则您只能在 try-with 块中打开资源。

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

try (PreparedStatement ps = ....) {
    // 在此处使用 ps,随意使用....
} // ps 在此处关闭

要么您这样做:

class WhateverTool implements AutoClosable {
    private PreparedStatement ps; // 长寿命

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

而您与这个类的唯一交互方式就是这样的形式:

try (WhateverTool t = new WhateverTool()) {
    // 在这里做任何您想做的事情...
} // 但在这里结束

在您的代码库中不允许与资源交互的任何其他选项。

Java 不会强制执行这一点,但是如果不遵循这些规则,您肯定会遇到错误。

英文:

As your second snippet shows, yup, you can do that.

You can create a preparedstatement, then wait ages and ages, then set each 'parameter' (call e.g. .setInt(1, ...), for every question mark in the SQL string you passed), and call one of the execute methods, and then... wait some more ages and do that again, as many times as you like.

A preparedstatement lives as long as the connection lives, or until you close it, which you should. The real rub lies there: Closing them. You do have to close them explicitly. They are like files and net sockets that way.

I you mess that up, your app won't immediately crash and no tests will fail. However, the connection is permanently 'damaged', as it has only room for a limited amount of prepared statements. As you keep making more and never closing them, eventually you run out, and bizarre SQL Exceptions will occur. Your app is a ticking time bomb. This is the worst class of bugs: Hard to test for, will inevitably explode your app, exactly as things get busy. When bossman starts asking the legal team if they can sue you for gross negligence, that's when you go: Oof, maybe I shouldn't toy with code styles that lead to bugs that cannot easily be tested for, are hard to spot, and will only cause issues riight when things are busy.

So, follow (rigidly) protocols to avoid such things:

  1. Any resource that is created (via either new SomeResource(), or via a method that is crystal clear that it is a creator, such as Files.newInputStream, must be closed by the same code, OR, that code must in turn be a closable resource.
  2. Sharpening up #1, unless you are a closable entity yourself, you can only open resources in a try-with block.

In other words, either you do this:

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

or you do this:

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

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

and your only interaction with this class is, itself, in the form of:

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

And there are no other options allowed in your code base to interact with resources.

Java doesn't enforce this, but you are definitely going to have a buggy mess if you don't follow these rules.

huangapple
  • 本文由 发表于 2020年9月29日 09:26:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/64111632.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定