Guia para a Interface ResultSet do JDBC

Guia para a Interface ResultSet do JDBC

1. Visão geral

OJava Database Connectivity (JDBC) API fornece acesso ao banco de dados a partir de um aplicativo Java. Podemos usar o JDBC para conectar-se a qualquer banco de dados, desde que o driver JDBC suportado esteja disponível.

OResultSet is a table of data generated by executing database queries. Neste tutorial, vamos dar uma olhada emResultSet API.

2. Gerando umResultSet

Primeiro, recuperamos umResultSet chamandoexecuteQuery() em qualquer objeto que implementa a interfaceStatement. Tanto oPreparedStatement quanto oCallableStatement são subinterfaces deStatement:

PreparedStatement pstmt = dbConnection.prepareStatement("select * from employees");
ResultSet rs = pstmt.executeQuery();

O objetoResultSet mantém um cursor que aponta para a linha atual do conjunto de resultados. Usaremosnext() em nossoResultSet para iterar pelos registros.

Em seguida, vamosuse the getX() methods while iterating through the results to fetch the values from the database columns, ondeX é o tipo de dados da coluna. Na verdade, forneceremos nomes de colunas de banco de dados para os métodosgetX():

while(rs.next()) {
    String name = rs.getString("name");
    Integer empId = rs.getInt("emp_id");
    Double salary = rs.getDouble("salary");
    String position = rs.getString("position");
}

Da mesma forma, the index number of the column can be used with the getX() methods em vez do nome da coluna. O número do índice é a sequência das colunas na instrução SQL select.

Se a instrução select não lista os nomes das colunas, o número do índice é a sequência de colunas na tabela. A numeração do índice da coluna começa em um:

Integer empId = rs.getInt(1);
String name = rs.getString(2);
String position = rs.getString(3);
Double salary = rs.getDouble(4);

3. Recuperando MetaDados deResultSet

Nesta seção, veremos como recuperar informações sobre as propriedades e tipos da coluna em umResultSet.

Primeiro, vamos usar o métodogetMetaData() em nossoResultSet para obter oResultSetMetaData:

ResultSetMetaData metaData = rs.getMetaData();

A seguir, vamos obter o número de colunas que estão em nossoResultSet:

Integer columnCount = metaData.getColumnCount();

Além disso, podemos usar qualquer um dos métodos abaixo em nosso objeto de metadados para recuperar propriedades de cada coluna:

  • getColumnName(int columnNumber) para obter o nome da coluna

  • getColumnLabel(int columnNumber) para acessar o rótulo da coluna, que é especificado apósAS na consulta SQL

  • getTableName(int columnNumber) para obter o nome da tabela à qual esta coluna pertence

  • getColumnClassName(int columnNumber) para adquirir o tipo de dados Java da coluna

  • getColumnTypeName(int columnNumber) para obter o tipo de dados da coluna no banco de dados

  • getColumnType(int columnNumber) para obter o tipo de dados SQL da coluna

  • isAutoIncrement(int columnNumber) indica se a coluna é de incremento automático

  • isCaseSensitive(int columnNumber) especifica se o caso da coluna é importante

  • isSearchable(int columnNumber) sugere se podemos usar a coluna na cláusulawhere da consulta SQL

  • isCurrency(int columnNumber) sinaliza se a coluna contém um valor em dinheiro

  • isNullable(int columnNumber) retornazero se a coluna não pode ser nula,one se a coluna pode conter um valor nulo etwo se a nulidade da coluna é desconhecida

  • isSigned(int columnNumber) retornatrue se os valores na coluna são assinados, caso contrário, retornafalse

Vamos iterar pelas colunas para obter suas propriedades:

for (int columnNumber = 1; columnNumber <= columnCount; columnNumber++) {
    String catalogName = metaData.getCatalogName(columnNumber);
    String className = metaData.getColumnClassName(columnNumber);
    String label = metaData.getColumnLabel(columnNumber);
    String name = metaData.getColumnName(columnNumber);
    String typeName = metaData.getColumnTypeName(columnNumber);
    int type = metaData.getColumnType(columnNumber);
    String tableName = metaData.getTableName(columnNumber);
    String schemaName = metaData.getSchemaName(columnNumber);
    boolean isAutoIncrement = metaData.isAutoIncrement(columnNumber);
    boolean isCaseSensitive = metaData.isCaseSensitive(columnNumber);
    boolean isCurrency = metaData.isCurrency(columnNumber);
    boolean isDefiniteWritable = metaData.isDefinitelyWritable(columnNumber);
    boolean isReadOnly = metaData.isReadOnly(columnNumber);
    boolean isSearchable = metaData.isSearchable(columnNumber);
    boolean isReadable = metaData.isReadOnly(columnNumber);
    boolean isSigned = metaData.isSigned(columnNumber);
    boolean isWritable = metaData.isWritable(columnNumber);
    int nullable = metaData.isNullable(columnNumber);
}

4. Navegando emResultSet

Quando obtemos umResultSet, a posição do cursor está antes da primeira linha. Além disso, por padrão, oResultSet se move apenas na direção para frente. Mas, podemos usar umResultSet rolável para outras opções de navegação.

Nesta seção, discutiremos as várias opções de navegação.

4.1. TiposResultSet

O tipoResultSet indica como iremos navegar pelo conjunto de dados:

  • TYPE_FORWARD_ONLY – a opção padrão, na qual o cursor se move do início ao fim

  • TYPE_SCROLL_INSENSITIVE – nosso cursor pode se mover pelo conjunto de dados nas direções para frente e para trás; se houver alterações nos dados subjacentes durante a movimentação pelo conjunto de dados, elas serão ignoradas; o conjunto de dados contém os dados do momento em que a consulta ao banco de dados retorna o resultado

  • TYPE_SCROLL_SENSITIVE – semelhante ao tipo insensível à rolagem, no entanto, para este tipo, o conjunto de dados reflete imediatamente quaisquer alterações nos dados subjacentes

Nem todos os bancos de dados oferecem suporte a todos os tiposResultSet. Então, vamos verificar se o tipo é suportado usandosupportsResultSetType em nosso objetoDatabaseMetaData:

DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);

4.2. ResultSet rolável

Para obter umResultSet rolável, precisamospass some additional parameters while preparing the Statement.

Por exemplo, obteríamos umResultSet rolável usandoTYPE_SCROLL_INSENSITIVE ouTYPE_SCROLL_SENSITIVE como um tipoResultSet:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_INSENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

4.3. Opções de Navegação

Podemos usar qualquer uma das opções abaixo em umResultSet rolável:

  • next() - prossegue para a próxima linha da posição atual

  • previous() - passa para a linha anterior

  • first() – navega para a primeira linha deResultSet

  • last() – salta para a última linha

  • beforeFirst() – move-se para o início; chamarnext() em nossoResultSet depois de chamar este método retorna a primeira linha de nossoResultSet

  • afterLast() – salta para o fim; chamarprevious() on our ResultSet after executando este método retorna a última linha de nossoResultSet

  • relative(int numOfRows) – vai para frente ou para trás da posição atual pelonumOfRows

  • absolute(int rowNumber) – salta pararowNumber especificado

Vejamos alguns exemplos:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the results from first to last
}
rs.beforeFirst(); // jumps back to the starting point, before the first row
rs.afterLast(); // jumps to the end of resultset

rs.first(); // navigates to the first row
rs.last(); // goes to the last row

rs.absolute(2); //jumps to 2nd row

rs.relative(-1); // jumps to the previous row
rs.relative(2); // jumps forward two rows

while (rs.previous()) {
    // iterates from current row to the first row in backward direction
}

4.4. ResultSet Contagem de linhas

Vamos usargetRow() para obter o número da linha atual de nossoResultSet.

Primeiro, navegaremos até a última linha deResultSete usaremosgetRow() para obter o número de registros:

rs.last();
int rowCount = rs.getRow();

5. Atualizando dados em umResultSet

Por padrão,ResultSet é somente leitura. No entanto, podemos usar umResultSet atualizável para inserir, atualizar e excluir as linhas.

5.1. ResultSet Concorrência

O modo de simultaneidade indica se nossoResultSet pode atualizar os dados.

A opçãoCONCUR_READ_ONLY é o padrão e deve ser usada se não precisarmos atualizar os dados usando nossoResultSet.

No entanto, se precisarmos atualizar os dados em nossoResultSet, a opçãoCONCUR_UPDATABLE deve ser usada.

Not all databases support all the concurrency modes for all ResultSet types. Portanto, precisamos verificar se nosso tipo e modo de simultaneidade desejados são suportados usando o métodosupportsResultSetConcurrency():

DatabaseMetaData dbmd = dbConnection.getMetaData();
boolean isSupported = dbmd.supportsResultSetConcurrency(
  ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

5.2. Obtendo umResultSet atualizável

Para obter umResultSet atualizável, precisamos passar um parâmetro adicional quando preparamos oStatement. Para isso, vamos usarCONCUR_UPDATABLE como o terceiro parâmetro ao criar uma instrução:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt.executeQuery();

5.3. Atualizando uma linha

Nesta seção, vamos atualizar uma linha usando oResultSet atualizável criado na seção anterior.

Podemos atualizar os dados em uma linha chamando os métodosupdateX(), passando os nomes das colunas e valores a serem atualizados. Podemos usar qualquer tipo de dados com suporte no lugar deX no métodoupdateX().

Vamos atualizar a coluna“salary”, que é do tipodouble:

rs.updateDouble("salary", 1100.0);

Observe que isso apenas atualiza os dados emResultSet, mas as modificações ainda não foram salvas de volta no banco de dados.

Finalmente, vamos chamarupdateRow() asave the updates to the database:

rs.updateRow();

Em vez dos nomes das colunas, podemos passar o índice da coluna para os métodosupdateX(). Isso é semelhante a usar o índice da coluna para obter os valores usando os métodosgetX(). Passar o nome da coluna ou índice para os métodosupdateX() produz o mesmo resultado:

rs.updateDouble(4, 1100.0);
rs.updateRow();

5.4. Inserindo uma linha

Agora, vamos inserir uma nova linha usando nossoResultSet atualizável.

Primeiro, usaremosmoveToInsertRow() para mover o cursor e inserir uma nova linha:

rs.moveToInsertRow();

Em seguida, devemos chamar os métodosupdateX() para adicionar as informações à linha. Precisamos fornecer dados para todas as colunas na tabela do banco de dados. Se não fornecermos dados para todas as colunas, o valor padrão da coluna será usado:

rs.updateString("name", "Venkat");
rs.updateString("position", "DBA");
rs.updateDouble("salary", 925.0);

Então, vamos chamarinsertRow() para inserir uma nova linha no banco de dados:

rs.insertRow();

Finalmente, vamos usarmoveToCurrentRow().. Isso levará a posição do cursor de volta à linha em que estávamos antes de começarmos a inserir uma nova linha usando o métodomoveToInsertRow():

rs.moveToCurrentRow();

5.5. Excluindo uma linha

Nesta seção, excluiremos uma linha usando nossoResultSet atualizável.

Primeiro, navegaremos até a linha que queremos excluir. Em seguida, chamaremos o métododeleteRow() para excluir a linha atual:

rs.absolute(2);
rs.deleteRow();

6. Holdability

A capacidade de retenção determina se nossoResultSet será aberto ou fechado no final de uma transação do banco de dados.

6.1. Tipos de capacidade de retenção

UseCLOSE_CURSORS_AT_COMMIT seResultSet não for necessário após a confirmação da transação.

UseHOLD_CURSORS_OVER_COMMIT para criar umResultSet armazenável. UmResultSet que pode ser mantido não é fechado, mesmo depois que a transação do banco de dados é confirmada.

Nem todos os bancos de dados suportam todos os tipos de capacidade de retenção.

Então, vamoscheck if the holdability type is supported usandosupportsResultSetHoldability() em nosso objetoDatabaseMetaData. Em seguida, obteremos a capacidade de retenção padrão do banco de dados usandogetResultSetHoldability():

boolean isCloseCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);
boolean isOpenCursorSupported
  = dbmd.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
boolean defaultHoldability
  = dbmd.getResultSetHoldability();

6.2. HoldableResultSet

Para criar umResultSet segurável, precisamos especificar o tipoholdability como o último parâmetro ao criar umStatement. Este parâmetro é especificado após o modo de simultaneidade.

Observe que se estivermos usando o Microsoft SQL Server (MSSQL), temos que definir a capacidade de retenção na conexão do banco de dados, em vez de noResultSet:

dbConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);

Vamos ver isso em ação. Primeiro, vamos criar umStatement, definindo a capacidade de retenção paraHOLD_CURSORS_OVER_COMMIT:

Statement pstmt = dbConnection.createStatement(
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_UPDATABLE,
  ResultSet.HOLD_CURSORS_OVER_COMMIT)

Agora, vamos atualizar uma linha enquanto recuperamos os dados. Isso é semelhante ao exemplo de atualização que discutimos anteriormente, exceto que continuaremos a iterar porResultSet depois de confirmar a transação de atualização para o banco de dados. Isso funciona bem nos bancos de dados MySQL e MSSQL:

dbConnection.setAutoCommit(false);
ResultSet rs = pstmt.executeQuery("select * from employees");
while (rs.next()) {
    if(rs.getString("name").equalsIgnoreCase("john")) {
        rs.updateString("name", "John Doe");
        rs.updateRow();
        dbConnection.commit();
    }
}
rs.last();

É importante notar que o MySQL suporta apenasHOLD_CURSORS_OVER_COMMIT. Portanto, mesmo se usarmosCLOSE_CURSORS_AT_COMMIT, ele será ignorado.

O banco de dados MSSQL oferece suporte aCLOSE_CURSORS_AT_COMMIT. Isso significa queResultSet será fechado quando confirmarmos a transação. Como resultado, uma tentativa de acessarResultSet depois de confirmar a transação resulta em um erro de ‘Cursor não está aberto’. Portanto, não podemos recuperar mais registros deResultSet.

7. Buscar tamanho

Normalmente, ao carregar dados em umResultSet, os drivers do banco de dados decidem o número de linhas a serem buscadas no banco de dados. Em um banco de dados MySQL, por exemplo, oResultSet normalmente carrega todos os registros na memória de uma vez.

Às vezes, no entanto, podemos precisar lidar com um grande número de registros que não cabem em nossa memória JVM. Nesse caso, podemos usar a propriedade fetch size em nossos objetosStatement ouResultSet para limitar o número de registros retornados inicialmente.

Sempre que resultados adicionais são necessários,ResultSet busca outro lote de registros do banco de dados. Usando a propriedade fetch size, podemosprovide a suggestion to the database driver on the number of rows to fetch per database trip. O tamanho da busca que especificarmos será aplicado às viagens subsequentes ao banco de dados.

Se não especificarmos o tamanho de busca para nossoResultSet, então o tamanho de busca deStatement é usado. Se não especificarmos o tamanho da busca paraStatement ouResultSet, o padrão do banco de dados será usado.

7.1. Usando Fetch Size emStatement

Agora, vamos ver o tamanho da busca emStatement em ação. Definiremos o tamanho de busca deStatement para 10 registros. Se nossa consulta retornar 100 registros, haverá 10 viagens de ida e volta ao banco de dados, carregando 10 registros de cada vez:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

while (rs.next()) {
    // iterate through the resultset
}

7.2. Usando Fetch Size emResultSet

Agora, vamos alterar o tamanho da busca em nosso exemplo anterior usandoResultSet.

Primeiro, usaremos o tamanho de busca em nossoStatement. Isso permite que nossoResultSet carregue inicialmente 10 registros após executar a consulta.

Então, vamos modificar o tamanho da busca emResultSet. Isso substituirá o tamanho da busca que especificamos anteriormente em nossoStatement. Portanto, todas as viagens subseqüentes carregarão 20 registros até que todos os registros sejam carregados.

Como resultado, haverá apenas 6 viagens ao banco de dados para carregar todos os registros:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

rs.setFetchSize(20);

while (rs.next()) {
    // iterate through the resultset
}

Finalmente, veremos como modificar o tamanho de busca deResultSet enquanto iteramos os resultados.

Semelhante ao exemplo anterior, primeiro definiremos o tamanho da busca para 10 em nossoStatement. Portanto, nossas 3 primeiras viagens ao banco de dados carregarão 10 registros por cada viagem.

E então, vamos modificar o tamanho de busca em nossoResultSet para 20 durante a leitura do 30º registro. Portanto, as próximas 4 viagens carregarão 20 registros por cada viagem.

Portanto, precisaremos de 7 viagens de banco de dados para carregar todos os 100 registros:

PreparedStatement pstmt = dbConnection.prepareStatement(
  "select * from employees",
  ResultSet.TYPE_SCROLL_SENSITIVE,
  ResultSet.CONCUR_READ_ONLY);
pstmt.setFetchSize(10);

ResultSet rs = pstmt.executeQuery();

int rowCount = 0;

while (rs.next()) {
    // iterate through the resultset
    if (rowCount == 30) {
        rs.setFetchSize(20);
    }
    rowCount++;
}

8. Conclusão

Neste artigo, vimos como usar a APIResultSet para recuperar e atualizar dados de um banco de dados. Vários dos recursos avançados que discutimos dependem do banco de dados que estamos usando. Portanto, precisamos verificar o suporte para esses recursos antes de usá-los.

Como sempre, o código está disponívelover on GitHub.