我应该如何在JDBC中使用try-with-resources?

Jon*_*nas 135 java jdbc java-7 try-with-resources

我有一种方法可以使用JDBC从数据库中获取用户:

public List<User> getUser(int userId) {
    String sql = "SELECT id, name FROM users WHERE id = ?";
    List<User> users = new ArrayList<User>();
    try {
        Connection con = DriverManager.getConnection(myConnectionURL);
        PreparedStatement ps = con.prepareStatement(sql); 
        ps.setInt(1, userId);
        ResultSet rs = ps.executeQuery();
        while(rs.next()) {
            users.add(new User(rs.getInt("id"), rs.getString("name")));
        }
        rs.close();
        ps.close();
        con.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}
Run Code Online (Sandbox Code Playgroud)

我应该如何使用Java 7 try-with-resources来改进此代码?

我已尝试使用下面的代码,但它使用了许多try块,并且不会提高可读性.我应该try-with-resources以其他方式使用吗?

public List<User> getUser(int userId) {
    String sql = "SELECT id, name FROM users WHERE id = ?";
    List<User> users = new ArrayList<>();
    try {
        try (Connection con = DriverManager.getConnection(myConnectionURL);
             PreparedStatement ps = con.prepareStatement(sql);) {
            ps.setInt(1, userId);
            try (ResultSet rs = ps.executeQuery();) {
                while(rs.next()) {
                    users.add(new User(rs.getInt("id"), rs.getString("name")));
                }
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}
Run Code Online (Sandbox Code Playgroud)

Jea*_*sky 181

我意识到这很久以前就已经回答了,但是想要建议一种避免嵌套的try-with-resources双块的额外方法.

public List<User> getUser(int userId) {
    try (Connection con = DriverManager.getConnection(myConnectionURL);
         PreparedStatement ps = createPreparedStatement(con, userId); 
         ResultSet rs = ps.executeQuery()) {

         // process the resultset here, all resources will be cleaned up

    } catch (SQLException e) {
        e.printStackTrace();
    }
}

private PreparedStatement createPreparedStatement(Connection con, int userId) throws SQLException {
    String sql = "SELECT id, username FROM users WHERE id = ?";
    PreparedStatement ps = con.prepareStatement(sql);
    ps.setInt(1, userId);
    return ps;
}
Run Code Online (Sandbox Code Playgroud)

  • 不,它被覆盖,问题是上面的代码是从一个不声明抛出SQLException的方法内部调用prepareStatement.此外,上面的代码至少有一个路径可以在不关闭预准备语句的情况下失败(如果在调用setInt时发生SQLException). (22认同)
  • @ArturoTena是 - 订单有保证 (2认同)
  • @JeanneBoyarsky有另外一种方法吗?如果不是,我需要为每个sql语句创建一个特定的createPreparedStatement方法 (2认同)
  • 关于 Trejkaz 的评论,“createPreparedStatement”无论你如何使用它都是不安全的。要修复它,您必须在 setInt(...) 周围添加一个 try-catch,捕获任何“SQLException”,并在发生时调用 ps.close() 并重新抛出异常。但这会导致代码几乎与OP想要改进的代码一样长且不优雅。 (2认同)

bpg*_*rgo 71

在您的示例中不需要外部try,因此您至少可以从3下降到2,并且您也不需要;在资源列表的末尾关闭.使用两个try块的优点是您的所有代码都预先存在,因此您不必引用单独的方法:

public List<User> getUser(int userId) {
    String sql = "SELECT id, username FROM users WHERE id = ?";
    List<User> users = new ArrayList<>();
    try (Connection con = DriverManager.getConnection(myConnectionURL);
         PreparedStatement ps = con.prepareStatement(sql)) {
        ps.setInt(1, userId);
        try (ResultSet rs = ps.executeQuery()) {
            while(rs.next()) {
                users.add(new User(rs.getInt("id"), rs.getString("name")));
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}
Run Code Online (Sandbox Code Playgroud)

  • 你如何调用[`Connection :: setAutoCommit`](http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setAutoCommit-boolean-)?在`con =`和`ps =`之间的`try`中不允许这样的调用.从可能使用连接池支持的DataSource获取连接时,我们无法假设如何设置autoCommit. (5认同)

小智 5

创建一个额外的包装类怎么样?

package com.naveen.research.sql;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public abstract class PreparedStatementWrapper implements AutoCloseable {

    protected PreparedStatement stat;

    public PreparedStatementWrapper(Connection con, String query, Object ... params) throws SQLException {
        this.stat = con.prepareStatement(query);
        this.prepareStatement(params);
    }

    protected abstract void prepareStatement(Object ... params) throws SQLException;

    public ResultSet executeQuery() throws SQLException {
        return this.stat.executeQuery();
    }

    public int executeUpdate() throws SQLException {
        return this.stat.executeUpdate();
    }

    @Override
    public void close() {
        try {
            this.stat.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


然后在调用类中,您可以将 prepareStatement 方法实现为:

try (Connection con = DriverManager.getConnection(JDBC_URL, prop);
    PreparedStatementWrapper stat = new PreparedStatementWrapper(con, query,
                new Object[] { 123L, "TEST" }) {
            @Override
            protected void prepareStatement(Object... params) throws SQLException {
                stat.setLong(1, Long.class.cast(params[0]));
                stat.setString(2, String.valueOf(params[1]));
            }
        };
        ResultSet rs = stat.executeQuery();) {
    while (rs.next())
        System.out.println(String.format("%s, %s", rs.getString(2), rs.getString(1)));
} catch (SQLException e) {
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

  • 上面评论中的任何内容都没有说它没有。 (2认同)

Bas*_*que 5

正如其他人所说,虽然try不需要外部代码,但您的代码基本上是正确的。这里还有一些想法。

DataSource

这里的其他答案是正确和好的,例如bpgergo接受的答案。但是没有显示 的​​使用DataSourceDriverManager在现代 Java 中通常推荐过度使用。

因此,为了完整起见,这里有一个从数据库服务器获取当前日期的完整示例。这里使用的数据库是Postgres。任何其他数据库都可以类似地工作。您可以将使用替换org.postgresql.ds.PGSimpleDataSourceDataSource适合您的数据库的实现。您的特定驱动程序或连接池可能会提供一个实现,如果您走那条路的话。

DataSource,在实现中被关闭,因为它永远不会“打开”。ADataSource不是资源,未连接到数据库,因此它不持有网络连接或数据库服务器上的资源。ADataSource是连接数据库时所需的简单信息,包括数据库服务器的网络名称或地址、用户名、用户密码以及最终建立连接时要指定的各种选项。所以,你的DataSource执行对象不走你的尝试,与资源括号内。

嵌套尝试资源

您的代码正确使用了嵌套的 try-with-resources 语句。

请注意,在下面的示例代码中,我们还两次使用了 try-with-resources 语法,一个嵌套在另一个中。外部try定义了两个资源:ConnectionPreparedStatement。内部try定义ResultSet资源。这是一种常见的代码结构。

如果从内部抛出异常,并且没有在那里捕获,则ResultSet资源将自动关闭(如果存在,则不为空)。之后,PreparedStatement将关闭,最后Connection关闭。资源会按照它们在 try-with-resource 语句中声明的相反顺序自动关闭。

这里的示例代码过于简单。正如所写的那样,它可以使用单个 try-with-resources 语句执行。但在实际工作中,您可能会在嵌套的一对try调用之间做更多的工作。例如,您可能从用户界面或 POJO 中提取值,然后?通过调用PreparedStatement::set…方法将这些值传递给SQL 中的占位符。

语法说明

尾随分号

请注意,try-with-resources 括号内的最后一个资源语句后面的分号是可选的。我将它包含在我自己的工作中有两个原因:一致性和它看起来完整,并且它使复制粘贴混合行变得更容易,而不必担心行尾分号。您的 IDE 可能会将最后一个分号标记为多余,但保留它也无妨。

Java 9 – 在 try-with-resources 中使用现有变量

Java 9中的新功能是对 try-with-resources 语法的增强。我们现在可以声明和填充try语句括号外的资源。我还没有发现这对 JDBC 资源有用,但请在您自己的工作中牢记这一点。

ResultSet 应该关闭自己,但可能不会

在理想的世界中,ResultSet正如文档所承诺的那样,它将自行关闭:

当生成它的 Statement 对象关闭、重新执行或用于从多个结果的序列中检索下一个结果时,ResultSet 对象将自动关闭。

不幸的是,过去一些 JDBC 驱动程序臭名昭著地未能实现这一承诺。其结果是,许多JDBC程序员学会了明确关闭所有的JDBC资源,包括ConnectionPreparedStatement,和ResultSet也。现代的 try-with-resources 语法使这样做变得更容易,并且代码更紧凑。请注意,Java 团队费心将标记ResultSetAutoCloseable,我建议我们使用它。在所有 JDBC 资源周围使用 try-with-resources 使您的代码更能自我记录您的意图。

代码示例

package work.basil.example;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Objects;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.doIt();
    }

    private void doIt ( )
    {
        System.out.println( "Hello World!" );

        org.postgresql.ds.PGSimpleDataSource dataSource = new org.postgresql.ds.PGSimpleDataSource();

        dataSource.setServerName( "1.2.3.4" );
        dataSource.setPortNumber( 5432 );

        dataSource.setDatabaseName( "example_db_" );
        dataSource.setUser( "scott" );
        dataSource.setPassword( "tiger" );

        dataSource.setApplicationName( "ExampleApp" );

        System.out.println( "INFO - Attempting to connect to database: " );
        if ( Objects.nonNull( dataSource ) )
        {
            String sql = "SELECT CURRENT_DATE ;";
            try (
                    Connection conn = dataSource.getConnection() ;
                    PreparedStatement ps = conn.prepareStatement( sql ) ;
            )
            {
                … make `PreparedStatement::set…` calls here.
                try (
                        ResultSet rs = ps.executeQuery() ;
                )
                {
                    if ( rs.next() )
                    {
                        LocalDate ld = rs.getObject( 1 , LocalDate.class );
                        System.out.println( "INFO - date is " + ld );
                    }
                }
            }
            catch ( SQLException e )
            {
                e.printStackTrace();
            }
        }

        System.out.println( "INFO - all done." );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @GuilhermeTaffarelBergamin 是的,这就是异常在 Java 中的工作原理。如果没有被本地代码捕获,它们就会“冒泡”到外部调用代码。冒泡过程将继续通过您调用的所有方法,直到最终逃离您的应用程序并到达 JVM 进行处理。 (2认同)