我有一种使用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;
}
我应该如何使用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;
}
答案 0 :(得分:172)
我意识到这很久以前已经回答了,但我想建议一种额外的方法来避免嵌套的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;
}
答案 1 :(得分:67)
在您的示例中不需要外部尝试,因此您至少可以从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;
}
答案 2 :(得分:3)
这是一个使用lambdas和JDK 8供应商的简洁方法,以适应外部尝试中的所有内容:
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()) {
}
答案 3 :(得分:2)
创建一个额外的包装类怎么样?
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();
}
答案 4 :(得分:2)
正如其他人所述,尽管不需要外部try
,但是您的代码基本上是正确的。这里还有一些想法。
DataSource
此处的其他答案是正确且正确的,例如bpgergo的accepted Answer。但是没有一个显示使用DataSource
的情况,在现代Java中,通常不建议使用DriverManager
。
因此,出于完整性考虑,下面是一个完整的示例,该示例从数据库服务器获取当前日期。此处使用的数据库为Postgres。任何其他数据库都将类似地工作。您可以将org.postgresql.ds.PGSimpleDataSource
的使用替换为适合您的数据库的DataSource
的实现。可能由您的特定驱动程序或连接池(如果您走那条路线)提供了一个实现。
DataSource
实现无需关闭,因为它永远不会“打开”。 DataSource
不是资源,没有连接到数据库,因此它既没有网络连接也没有数据库服务器上的资源。 DataSource
只是与数据库建立连接时所需的信息,其中包含数据库服务器的网络名称或地址,用户名,用户密码以及最终建立连接时要指定的各种选项。因此,您的DataSource
实现对象不会不放入try-with-resources括号内。
您的代码正确使用了嵌套的try-with-resources语句。
在下面的示例代码中请注意,我们还使用了try-with-resources语法两次,一个嵌套在另一个内部。外部try
定义两个资源:Connection
和PreparedStatement
。内部的try
定义了ResultSet
资源。这是一种常见的代码结构。
如果从内部抛出异常并且没有在其中捕获,则ResultSet
资源将自动关闭(如果存在,则不为null)。之后,PreparedStatement
将被关闭,最后Connection
被关闭。在try-with-resource语句中以相反的顺序自动关闭资源。
此处的示例代码过于简单。如所写,可以使用单个try-with-resources语句执行。但是在实际工作中,您可能会在一对嵌套的try
调用之间进行更多的工作。例如,您可能正在从用户界面或POJO中提取值,然后通过对?
方法的调用将这些值传递给SQL中的PreparedStatement::set…
个占位符。
请注意,在try-with-resources括号内尾随最后一个resource语句的分号是可选的。我将其包含在我自己的工作中的原因有两个:一致性并且看起来很完整,并且使复制粘贴行更加容易,而不必担心行尾分号。您的IDE可能会将最后一个分号标记为多余,但保留它不会有任何危害。
New in Java 9是对try-with-resources语法的增强。现在,我们可以在try
语句的括号之外声明和填充资源。我还没有发现这对JDBC资源有用,但是在您自己的工作中要牢记这一点。
ResultSet
应该自行关闭,但可能不会关闭在理想的世界中,ResultSet
会按照文档中的说明自行关闭:
当ResultSet对象关闭,重新执行或用于从多个结果序列中检索下一个结果时,将自动关闭ResultSet对象。
不幸的是,过去臭名昭著的一些JDBC驱动程序未能实现这一承诺。结果,许多JDBC程序员学会了显式关闭其所有JDBC资源,包括Connection
,PreparedStatement
和ResultSet
。现代的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." );
}
}