确保数据库对象关闭的首选方法是什么?

What is the preferred method of ensuring that database objects are closed?

我很好奇有关数据库交互的最佳做法。我一直在使用一种模式,我相信这种模式可以确保在我处理完所有适当的对象后关闭它们。然而,一位同事最近用 "making sure we always close database objects" 的注释重构了我的代码。由于某种原因,我需要知道一种模式是否比另一种模式 "better"。我一直在使用的模式是否有问题?一种模式比另一种模式有优势吗?

我一直遵循的模式:

public void doStuff() {
    try {
        final Connection connection = this.getConnection();

        try {
            final PreparedStatement ps = connection.prepareStatement("SELECT COLA, COLB FROM TBL WHERE COLC = ?");

            try {
                ps.setString(1, "asdf");
                final ResultSet rs = ps.executeQuery();
                try {
                    // get data from rs 
                } finally {
                    rs.close();
                }
            } finally {
                ps.close();
            }
        } finally {
            connection.close();
        }
    } catch (SQLException e) {
        // do something with the error
    }
}

我的同事将我的代码修改为的模式:

public void doStuff() {
    Connection connection = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        connection = this.getConnection();
        ps = connection.prepareStatement("SELECT COLA, COLB FROM TBL WHERE COLC = ?");
        ps.setString(1, "asdf");
        rs = ps.executeQuery();
        // get data from rs
    } finally {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                // do something with the error
            }

        }
        if (ps!= null) {
            try {
                ps.close();
            } catch (SQLException e) {
                // do something with the error
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                // do something with the error
            }
        }

    }
}

如果您使用的是 Java 6 或更早版本,请使用后者,因为它更易于阅读和维护。请注意,后者可以通过一些重构来改进,以处理每次调用 close 方法时繁琐的 try-catch

如果您使用的是 Java 7 或更高版本,请使用 try-with-resources:

try (Connection con = ...;
     PreparedStatement pstmt = ...) {
    pstmt.setXyz(...);
    ResultSet rs = pstmt.executeQuery();
    //read data from resultset
    //and then close it
    rs.close();
} catch (Exception e) {
    //handle the exception properly...
}

如果您想确保关闭 ResultSet,您可以使用嵌套的 try-with-resources:

try (Connection con = ...;
     PreparedStatement pstmt = ...) {
    pstmt.setXyz(...);
    try(ResultSet rs = pstmt.executeQuery()) {
        //read data from resultset
    }
} catch (Exception e) {
    //handle the exception properly...
}

后者更易读;深层嵌套很难推理。

我更喜欢可关闭对象的安全包装器,例如,如果可关闭对象为空,它们什么也不做。这也使得主线代码更易于阅读。

Luigi 的回答当然从 Java 7 开始最有意义。

将数据库资源的关闭抽象为专用的管理器对象通常更简单、更清晰,该对象将包含任何 NPE 等可能抛出的东西。

作为开源项目 OpenFire 的一部分存在一个写得很好的:

https://github.com/igniterealtime/Openfire/blob/master/src/java/org/jivesoftware/database/DbConnectionManager.java#L243

此 DbConnectionManager 中的示例辅助方法:

public static void closeResultSet(ResultSet rs) {
    if (rs != null) {
        try {
                rs.close();
            }
        catch (SQLException e) {
            Log.error(e.getMessage(), e);
        }
    }
}

因此,在您的 finally 块中,您只需将资源传递回您的管理器,它会处理丑陋的逻辑以测试空值和捕获异常等。

喜欢:

Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    con = DbConnectionManager.getConnection();
    ps = con.prepareStatement(yourStatement);
    rs = ps.executeQuery();
    if (rs != null) {
        while (rs.next()) {
            // do stuff
        }
    }
} catch (SQLException e) {
    LOG.error(e.getMessage(), e);
} finally {
    DbConnectionManager.closeConnection(rs, ps, con);
}

模拟Go的defer语句:D

try(Defer defer = new Defer())
{
    Connection connection = ...;
    defer.add( connection::close );

    ....

    Path tmpFile = ...;
    defer.add( ()->Files.delete(tmpFile) );

    ....

} // Defer.close() => executing registered actions, from last to first

如何实现 Defer 留给读者作为练习:)