我应该如何在 JDBC 中使用 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;
}

我应该如何使用 Java7< strong > 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;
}
162757 次浏览

在您的示例中不需要进行外部尝试,因此您至少可以从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;
}

我知道这个问题很久以前就有人回答了,但是我想建议一种额外的方法,避免嵌套的 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;
}

如何创建一个额外的包装类?

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


然后,在调用类中,您可以按如下方式实现 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();
}

下面是一个使用 lambdas 和 JDK 8 Supplier 来适应外部尝试的简明方法:

try (Connection con = DriverManager.getConnection(JDBC_URL, prop);
PreparedStatement stmt = ((Supplier<PreparedStatement>)() -> {
try {
PreparedStatement s = con.prepareStatement("SELECT userid, name, features FROM users WHERE userid = ?");
s.setInt(1, userid);
return s;
} catch (SQLException e) { throw new RuntimeException(e); }
}).get();
ResultSet resultSet = stmt.executeQuery()) {
}

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

DataSource

这里的其他答案都是正确和好的,比如 bpgergo 的 接受答案。但是它们都没有显示使用 DataSource,在现代 Java 中通常建议使用 DriverManager

因此,为了完整起见,下面是一个从数据库服务器获取当前日期的完整示例。这里使用的数据库是 邮差。任何其他数据库的工作方式都是类似的。您将使用适合您的数据库的 DataSource实现来替换 org.postgresql.ds.PGSimpleDataSource的使用。一个实现很可能是由您的特定驱动程序提供的,或者如果您使用该路由,则由连接池提供。

DataSource实现需要关闭 没有,因为它永远不会“打开”。DataSource不是资源,不连接到数据库,因此它不保存网络连接,也不保存数据库服务器上的资源。DataSource只是连接到数据库时所需的信息,包括数据库服务器的网络名称或地址、用户名、用户密码以及最终连接时需要指定的各种选项。所以您的 DataSource实现对象将 没有放在 try-with-resources 括号内。

DataSource的用途是外部化数据库连接信息。如果在源代码中硬编码用户名、密码等,那么对数据库服务器配置的更改意味着必须重新编译和重新部署代码,这可不好玩。相反,这样的数据库配置细节应该存储在源代码 在外面中,然后在运行时检索。可以通过 JNDI从命名和目录服务器(如 LDAP)检索配置详细信息。或者,您可以从运行您的应用程序的 Servlet 容器雅加达 EE服务器检索。

嵌套的使用资源的尝试

您的代码正确地使用了嵌套的 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 可能会将最后一个分号标记为多余的,但是将其保留下来也没有什么坏处。

Java9-在 try-with-resources 中使用现有的 var

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

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

在理想的情况下,ResultSet会像文档承诺的那样关闭:

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

不幸的是,在过去,一些 JDBC 驱动程序臭名昭著地未能实现这一承诺。因此,许多 JDBC 程序员学会了显式地关闭所有 JDBC 资源,包括 ConnectionPreparedStatementResultSet。现代的 try-with-resources 语法使得这样做更加容易,代码也更加紧凑。请注意,Java 团队费了很大劲才将 ResultSet标记为 AutoCloseable,我建议我们利用这一点。在所有 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." );
}
}