Можно "перемотать" ResultSet и повторно прочитать из него данные:
try (ResultSet rs = statement.executeQuery(query)) {
while (rs.next()) {
System.out.println("User: " + rs.getString("login"));
}
rs.beforeFirst(); // <-- перематываем
while (rs.next()) {
System.out.println("Hello, " + rs.getString("login"));
}
}
Но это не сработает, если драйвер создал ResultSet типа TYPE_FORWARD_ONLY или реализация ResultSet не поддерживает перемотку в принципе. В этом случае придётся прочитать весь результат полностью в промежуточное хранилище и дальше работать уже с ним:
class User {
private final String login;
private final String name;
private final String email;
public User(ResultSet rs) throws SQLException {
login = rs.getString("login");
name = rs.getString("name");
email = rs.getString("email");
}
public String getLogin() {
return login;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
List<User> users = new ArrayList<>();
try (ResultSet rs = statement.executeQuery(query)) {
while (rs.next()) {
users.add(new User(rs));
}
}
for (User u : users) {
System.out.println("Hello, " + u.getLogin());
}
Вариант с использованием отдельного класса для хранения результатов используется повсеместно и имеет своё собственное название - DTO (Data Transfer Object).