Java中使用try-with-resources的prepared statement无法工作

6
昨天有多个Stack上的人推荐使用try-with-resources。现在我已经对所有数据库操作都采用这种方式。今天我想将Statement更改为PreparedStatement以使查询更加安全。但是当我尝试在try-with-resources中使用预编译语句时,我一直收到错误信息,例如'identifier expected'或';'或')'。

我做错了什么?还是不可能实现?这是我的代码:

    try (Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);
        PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE id = ? LIMIT 1");
        stmt.setInt(1, user);
        ResultSet rs = stmt.executeQuery()) {

        // if no record found
        if(!rs.isBeforeFirst()) {
           return false;
        }
        // if record found
        else {
            return true;
        }

    } catch (SQLException e) {
        // log error but dont do anything, maybe later
        String error = "SQLException: " + e.getMessage() + "\nSQLState: " + e.getSQLState() + "\nVendorError: " + e.getErrorCode();
        return false;

    }

1
在第一行中,将“;”更改为“){”。(愚蠢的打字错误?) - user253751
3个回答

10

使用try-with-resource语句来声明(Autocloseable)资源。ConnectionPreparedStatementResultSet都是Autoclosable的,所以没问题。

但是stmt.setInt(1, user)并不是一个资源,而是一个简单的语句。你不能在try-with-resource语句中放置这样的简单语句(没有资源声明)!

解决方案:创建多个try-with-resource语句!

try (Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS)) {
    executeStatement(conn);
} catch (SQLException e) {
    // log error but dont do anything, maybe later
    String error = "SQLException: " + e.getMessage() + "\nSQLState: " + e.getSQLState() + "\nVendorError: " + e.getErrorCode();
    return false;
}

private void executeStatement(Connection con) throws SQLException {
    try (PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE id=? LIMIT 1")) {
        stmt.setInt(1, user);
        try (ResultSet rs = stmt.executeQuery()) {
            // process result
        }
    }
}
请注意,技术上并不需要像我一样将SQL语句的执行放在一个单独的方法中。如果打开连接和创建PreparedStatement都在同一个try-with-resource语句中,它也可以正常工作。我只是认为将连接管理与其余代码分离是一种良好的实践。

1
为什么?因为您可以将ConnectionPreparedStatement放置在同一资源创建块中,然后使用stmt - Ross Drew
2
没错,但通常在实际应用中,打开JDBC连接和执行SQL操作是/应该分开的。当然,你也可以把所有东西放在一个方法中。 - isnot2bad
1
但感谢您的评论。我已将其添加到我的答案中,以避免混淆。 - isnot2bad

1

try this code:

try (Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS)) {
     PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE id = ? LIMIT 1");

        stmt.setInt(1, user);
        ResultSet rs = pstmt.executeQuery())

        // if no record found
        if(!rs.isBeforeFirst()) {
           return false;
        }
        // if record found
        else {
            return true;
        }

    } catch (SQLException e) {
        // log error but dont do anything, maybe later
        String error = "SQLException: " + e.getMessage() + "\nSQLState: " + e.getSQLState() + "\nVendorError: " + e.getErrorCode();
        return false;

    }

请注意,在这里,资源是您的 Connection,并且您必须在 try 块中使用它。

2
实际上,你应该把 PreparedStatement 也放在资源创建块内部,这样可以确保它被正确关闭。 - Ross Drew
好的,同意。此外,在这里发现了一个关于同一主题的帖子:https://dev59.com/GGsz5IYBdhLWcg3wLU12 也许有人可以将其标记为重复。我还没有那个权限。 - Pat
那篇帖子并没有涵盖这个特定的问题。 - Ross Drew
@Ross 我的意思是将这个问题标记为重复。这个问题询问了JDBC的try-with-resources的正确使用方式,就像我发布链接的那个问题一样。 - Pat
1
此外,当抛出异常时,ResultSet没有被关闭。 - Vlasec

1
移动
stmt.setInt(1, user);
ResultSet rs = stmt.executeQuery()

...在try{ /*HERE*/ }内部

这是因为stmt是被创建的资源try (/*HERE*/) {},用于try{ /*HERE*/ }

带资源的try语句

try (/*Create resources in here such as conn and stmt*/)
{
  //Use the resources created above such as stmt
}

重点是在资源创建块中创建的所有内容都实现了AutoClosable接口,当try块退出时,它们都会调用close()方法。在你的代码中,stmt.setInt(1, user)不是一个AutoCloseable资源,因此出现了问题。

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