Java JDBC ResultSet
Itere e leia linhas de uma consulta SQL em Java com a interface ResultSet.
Um ResultSet é um cursor sobre as linhas retornadas por uma consulta. Ele não mantém todas as linhas na memória de uma vez; aponta para uma linha por vez e você avança por ele. Importante: um ResultSet recém-criado está posicionado antes da primeira linha — você precisa chamar next() uma vez para chegar à primeira. É por isso que todo laço de leitura usa while (rs.next()).
Você obtém um ResultSet de uma consulta executada por meio de um Statement ou de um PreparedStatement. Esta página explica como percorrer esse cursor e ler colunas com segurança.
O laço de leitura
String sql = "SELECT id, name, score FROM player ORDER BY score DESC";
try (Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) { // advance; false when no more rows
int id = rs.getInt("id"); // read by column name...
String name = rs.getString(2); // ...or by 1-based index
int score = rs.getInt("score");
System.out.println(id + " " + name + " " + score);
}
}Lendo colunas: por nome ou por índice
Ambas as formas funcionam. Nomes de coluna são mais claros e resistem a uma reordenação do SELECT; índices de coluna (baseados em 1, como tudo no JDBC) são marginalmente mais rápidos. Nomes vencem em manutenibilidade em quase todo código. Se você precisar dos nomes, tipos ou quantidade de colunas em tempo de execução — por exemplo, para renderizar uma tabela genérica — leia-os no ResultSetMetaData obtido com rs.getMetaData().
O problema do NULL e wasNull()
Os getters primitivos não podem retornar null. getInt retorna 0 para um NULL SQL, getDouble retorna 0.0 e assim por diante — indistinguível de um zero real. Quando uma coluna é anulável e a diferença importa, chame rs.wasNull() imediatamente após o getter:
int score = rs.getInt("score");
if (rs.wasNull()) { /* it was SQL NULL, not a zero */ }(Para tipos de objeto, getObject retorna null diretamente, o que costuma ser mais limpo.)
Tipo de cursor e concorrência
Por padrão, um ResultSet é TYPE_FORWARD_ONLY e CONCUR_READ_ONLY — você avança e apenas lê. Solicite TYPE_SCROLL_INSENSITIVE para chamar previous(), absolute(n) ou first(), ou CONCUR_UPDATABLE para editar linhas no lugar. Esses modos custam mais, portanto solicite-os somente quando necessário.
Feche-o (try-with-resources faz isso)
Um ResultSet mantém um cursor do lado do servidor; deixá-lo aberto desperdiça recursos do banco de dados. Fechar o Statement correspondente o fecha também, e o try-with-resources cuida de ambos. Nunca retorne um ResultSet aberto de um método cuja Connection já foi fechada.
Um exemplo completo: o cursor forward-only e wasNull
Este programa modela o cursor do ResultSet com uma pequena lista em memória — começando antes da primeira linha, avançando com um passo estilo next() — e reproduz o comportamento de getInt retornar 0 para NULL com uma verificação de wasNull, além dos constantes de cursor.
O que observar na execução:
- O cursor começa no índice
-1— antes da primeira linha — e o passo++cursorespelhars.next(): avança primeiro, depois lê. É exatamente por isso que um laço real éwhile (rs.next())e nunca lê uma coluna antes do primeironext(). - As linhas são lidas uma de cada vez, não carregadas como uma lista. O modelo usa uma lista por simplicidade, mas um
ResultSetreal transmite linhas do servidor, o que permite lidar com conjuntos de resultados muito maiores que a memória. - A pontuação
nullde Linus foi impressa como0— a mesma armadilha quegetIntcria. Sem a flag você não poderia distinguir uma pontuação ausente de um zero genuíno. - O marcador
(wasNull)é o desambiguador. No JDBC real você chamars.wasNull()logo após o getter, pois ele relata sobre a coluna lida mais recentemente — leia outra coluna antes e a resposta muda. - As constantes (
TYPE_FORWARD_ONLY,CONCUR_READ_ONLY,FETCH_FORWARD) descrevem o cursor padrão e mais barato: avançar, somente leitura. Você opta por cursores roláveis ou atualizáveis deliberadamente, pois eles pedem mais do servidor.