Как я могу использовать try-with-resources с JDBC?

У меня есть метод для получения пользователей из базы данных с 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;
}

Ответы

Ответ 1

Я понимаю, что это было давно ответило, но вы хотите предложить дополнительный подход, который позволяет избежать вложенного двойного блока 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;
}

Ответ 2

В вашем примере нет необходимости в внешней попытке, поэтому вы можете по крайней мере спуститься с 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;
}

Ответ 3

Вот краткое описание использования 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()) {
    }

Ответ 4

Как насчет создания дополнительного класса-обёртки?

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

Ответ 5

В Java 9 это становится лучше и естественнее. Вы можете эффективно использовать конечные переменные в попытке с помощью блока ресурсов, и они все равно будут автоматически закрыты. Это означает, что вы можете объявить все впереди.

public List<String> getUsers(int userId) {
    List<String> users = new ArrayList<>();
    Connection con = DriverManager.getConnection(myConnectionURL);
    String sql = "SELECT id, username FROM users WHERE id = ?";
    PreparedStatement ps = con.prepareStatement(sql);
    ps.setInt(1, userId);
    ResultSet rs = ps.executeQuery();

    try (con; ps; rs) {
        while (rs.next()) {
          users.add(rs.getString(1));
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
  }