使用单个JDBC Statement对象执行多个查询

29
在JDBC中,我能否使用单个Statement对象多次调用executeQuery("")?这样做安全吗?或者我应该在每个查询后关闭语句对象,并创建新对象来执行另一个查询。
例如:
Connection con;
Statement s;
ResultSet rs;
ResultSet rs2;
try
{
    con = getConnection();
    // Initially I was creating the Statement object in an
    // incorrect way. It was just intended to be a pseudocode.
    // But too many answerers misinterpretted it wrongly. Sorry 
    // for that. I corrected the question now. Following is the 
    // wrong way, commented out now. 
    // s = con.prepareStatement();
    
    // Following is the way which is correct and fits for my question.
    s = con.createStatement();

    try
    {
        rs = s.executeQuery(".......................");

        // process the result set rs
    }
    finally
    {
        close(rs);
    }

    // I know what to do to rs here
    // But I am asking, should I close the Statement s here? Or can I use it again for the next query?

    try
    {
        rs2 = s.executeQuery(".......................");

        // process the result set rs2
    }
    finally
    {
        close(rs2);
    }
}
finally
{
    close(s);
    close(con);
}

1
这个线程可能很有用:http://www.java-forums.org/jdbc/69824-proper-use-preparedstatment.html - SnakeDoc
4
出于性能考虑,可以并且推荐重复使用“Statement”实例。 - user330315
那个链接已经失效了。我只提到它是因为它显示了“数据库错误,数据库遇到了问题”,这相当具有讽刺意味。 - gcr
5个回答

29

是的,你可以重复使用Statement(具体来说是PreparedStatement),在JDBC中一般都应该这样做。如果你不重复使用语句并立即创建另一个完全相同的Statement对象,那么它会效率低下且风格不好。至于关闭它,在finally块中关闭它是适当的,就像你在这个片段中一样。

关于你所问的示例,请查看此链接:jOOq Docs


重复使用ResultSet怎么样?这也是一个好的实践吗?在上面提供的代码中,该人创建了两个ResultSet对象。 - Erick
3
如果在完成后正确关闭它,重复使用 ResultSet 是可以的。只要操作是串行执行而不是并行执行,你就可以重复使用单个 ResultSetStatement。例如,用你的 Statement 实例化并运行查询 1,从 ResultSet 中获取值 xyz,然后关闭该语句,再用 value xyz 实例化并运行查询 2。 - Durandal
那么不是 PreparedStatementStatement 呢? - Tech Expert Wizard

10
我不确定你为什么在问。API的设计和文档显示,可以(甚至是有意)重复使用一个 Statement 对象进行多个 execute executeUpdate executeQuery 调用。如果不允许这样做,那么Java文档中会有明确的说明(并且可能API会有所不同)。
此外, Statement 的apidoc说:

Statement 接口中的所有执行方法都会隐式关闭语句当前的 ResultSet 对象(如果存在打开的结果集)。

这表明您可以多次使用它。
TL;DR:是的,您可以在单个 Statement 对象上多次调用 execute ,只要您意识到任何先前打开的 ResultSet 将被关闭即可。
您的示例错误地使用了PreparedStatement,您不应该在PreparedStatement上调用任何接受Stringexecute...方法

SQLException - 如果[...]在PreparedStatementCallableStatement上调用该方法

但是对于PreparedStatement的解答也是:PreparedStatement的整个目的是预编译带有参数占位符的语句,并在具有不同参数值的多个执行中重复使用它。

非常感谢您对我所问问题及遗漏部分的详细解答。经过多年的重新考虑,我很想将其标记为接受的答案,即使其他回答获得了更多票数。因为这个回答还解决了第一次提问背后的困惑。 - Aqeel Ashiq
此外,这个答案还揭示了问题的错误部分,进行了更正,并回答了正确和错误版本的问题。再次感谢。我想给它点赞10次。 - Aqeel Ashiq

4

我在API文档中找不到任何关于不应多次调用给定的PreparedStatement实例上的executeQuery()的说明。

然而,您的代码没有关闭PreparedStatement - 在这种情况下,调用executeQuery()会抛出SQLException,但返回的是ResultSet。当您重新执行PreparedStatement时,ResultSet将会自动关闭。根据您的情况,当您不再需要它时,应该关闭它。我会关闭它,因为我认为不这样做是不好的风格。

更新 哦,我在两个try块之间错过了您的评论。如果此时关闭您的PreparedStatement,则不能再次调用executeQuery()而不会收到SQLException。


1
您在中间发表的评论问是否应该在那里关闭它。但无论如何,是的,重复使用它是安全的。 - Grmpfhmbl

4
一个Prepared Statement告诉数据库记住你的查询并准备好接受参数化变量在该查询中执行。它很像存储过程。 Prepared Statement实现了两个主要目的:
1.它自动转义您的查询变量以帮助防止SQL注入。
2.它告诉数据库记住查询并准备好接受变量。
第二个目的非常重要,因为它意味着数据库只需要解释一次查询,然后就可以准备好执行。因此它提高了性能。
您不应该在每次执行调用之间关闭PreparedStatement和/或数据库连接。这样做非常低效,并且会比使用普通的Statement造成更多的开销,因为每次都要指示数据库创建一个过程并记住它。即使数据库已配置为记忆您的查询,即使您关闭了PreparedStatement,您仍会遭受网络开销以及小幅处理时间。
简而言之,在使用完毕前,请保持ConnectionPreparedStatement打开。
编辑:关于不从执行返回ResultSet,这是可以的。 executeQuery将为刚刚执行的任何查询返回ResultSet

这并没有真正回答问题。 - Mark Rotteveel
2
@MarkRotteveel 怎么了?OP问是否可以重复使用PreparedStatement,我已经解释了可以,并说明了原因。 - SnakeDoc
重新阅读问题和您的答案后,可能是我混淆了。OP创建了一个PreparedStatement(以无效的方式),但通过使用接受查询字符串的执行方法将其用作普通的Statement对象(这是不允许的,请参见我的答案)。我最初认为这个问题是关于Statement的,在那种情况下,您的答案并没有回答它。 - Mark Rotteveel
1
@MarkRotteveel 嗯,原帖中的代码片段有很多错误。由于“PreparedStatement”在大约95%的情况下都是最佳实践,所以我选择着重讲解这个问题。 - SnakeDoc

2

首先,我对你的代码感到困惑。

s = con.prepareStatement();

它工作正常吗?我在JAVA API中找不到这样的功能,至少需要一个参数。也许你想调用这个函数。

s = con.createStatement();

我刚刚运行了访问DB2的代码,使用了一个单一的Statement实例两次操作而没有在两次操作之间关闭它。这个方法很有效。我认为你也可以尝试一下。

    String sql = "";
    String sql2 = "";
    String driver = "com.ibm.db2.jcc.DB2Driver";
    String url = "jdbc:db2://ip:port/DBNAME";
    String user = "user";
    String password = "password";
    Class.forName(driver).newInstance();
    Connection conn = DriverManager.getConnection(url, user, password);
    Statement statement = conn.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);
    int count = 0;
    while (resultSet.next()) {
        count++;
    }
    System.out.println("Result row count of query number one is: " + count);
    count = 0;
    resultSet = statement.executeQuery(sql2);
    while (resultSet.next()) {
        count++;
    }
    System.out.println("Result row count of query number two is: " + count);

2
为了易读性,他使用了这种不起作用的伪代码。close(rs) 应该改为 rs.close(),并且你需要在 finally 中进行 null 检查和另一个 try/catch 等操作。 - Grmpfhmbl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接