Acesso simples e rápido às linhas de uma query

Abaixo segue duas classes para encapsular e facilitar o acesso a banco de dados via JDBC, a classe DBQuery encapsula uma query onde a mesma é capaz de gerar um iterator para navegação nas linhas da consulta de forma rápida, leve e eficiente.

O iterator é implementado de uma forma em que ele não guarde os dados da consulta em memória, possibilitando o uso dessa classe para queries que retornem muitas linhas de resultado.

Outra coisa interessante é a possibilidade de se trabalhar com genéricos, caso sua consulta ao banco retorne um tipo homogêneo de dados (por exemplo, todos os campos sejam do tipo String).

classe DBQuery.java

/**
 *
 * @author Paulo Canedo C Rodrigues
 */
public class DBQuery<t> implements Iterable</t><t> {
 
    private Connection connection;
    private String query;
 
    public DBQuery(Connection connection, String query) {
        this.connection = connection;
        this.query = query;
    }
 
    public Iterator</t><t> iterator() {
        try {
            Statement stm = connection.createStatement();
            ResultSet rs = stm.executeQuery(query);
            return new DBRowIterator(rs);
        } catch (SQLException ex) {
            return null;
        }
    }
}</t>

classe DBRowIterator.java

/**
 *
 * @author Paulo Canedo C Rodrigues
 */
public class DBRowIterator<t> implements Iterator<map <String, T>> {
 
    private ResultSet rs;
    private Map<string , T> rowFields = new HashMap</string><string , T>() {
 
        //Sobrescrita do metodo get do mapa para transformar o get em case insensitive
        @Override
        public T get(Object key) {
            return super.get(((String) key).toUpperCase());
        }
    };
    private String[] columnsName;
    private boolean objectReaded = false;
    private int columnCount;
 
    DBRowIterator(ResultSet rs) throws SQLException {
        this.rs = rs;
 
        columnCount = rs.getMetaData().getColumnCount();
        columnsName = new String[columnCount];
 
        for (int i = 0; i < columnCount; i++) {
            columnsName[i] = rs.getMetaData().getColumnName(i + 1).toUpperCase();
        }
    }
 
    public boolean hasNext() {
        if (objectReaded == false && rowFields.size() != 0) {
            return true;
        }
 
        try {
            boolean flag = rs.next();
            if (flag = true) {
                rowFields.clear();
                for (int i = 0; i < columnCount; i++) {
                    Object o = rs.getObject(i + 1);
                    rowFields.put(columnsName[i], (T) o);
                }
            } else {
                if (rowFields != null) {
                    try {
                        rs.getStatement().close();
                        rs.close();
                    } catch (Exception ex) {
                    }
                }
                rowFields = null;
            }
            return flag;
        } catch (Exception ex) {
        }
        return false;
    }
 
    public Map<String, T> next() {
        objectReaded = true;
        return rowFields;
    }
 
    public void remove() {
    }
}
</string></map></t>

Abaixo segue dois exemplos de utilização, por favor, deixem comentário sobre o que achou das classes e da ideia.

java.sql.Connection connection = null;
 
//Exemplo 1 com genérico sem casting - apenas para tipo de dados homogêneos
DBQuery dbQuery1 = new DBQuery(connection, "SELECT nome, sobrenome, endereco FROM tb_agenda ORDER BY nome");
for (Iterator<map <String, String>> it = dbQuery1.iterator(); it.hasNext();) {
    Map<string , String> linhaDaConsulta = it.next();
    String nome = linhaDaConsulta.get("nome"); //nome do campo da consulta: case insensitive
    String sobrenome = linhaDaConsulta.get("sobrenome");
    String endereco = linhaDaConsulta.get("endereco");
    System.out.println(String.format("Nome: %s; Sobrenome: %s; Endereço: %s", nome, sobrenome, endereco));
}
 
//Exemplo 2 sem genérico com casting
DBQuery dbQuery2 = new DBQuery(connection, "SELECT nome, idade, peso FROM tb_pessoa ORDER BY nome");
for (Iterator<map <String, Object>> it = dbQuery2.iterator(); it.hasNext();) {
    Map<string , Object> linhaDaConsulta = it.next();
    String nome = (String) linhaDaConsulta.get("nome");
    Integer idade = (Integer) linhaDaConsulta.get("idade");
    Float peso = (Float) linhaDaConsulta.get("peso");
    System.out.println(String.format("Nome: %s; Idade: %d; Peso: %.2f", nome, idade, peso));
}</string></map></string></map>

Utilizando o banco Derby de forma portável

Introdução

Em algumas situações desenvolvemos aplicações de pequeno porte onde queremos utilizar um banco de dados, mas não temos interesse em instalar um SGBD em cada computador que for rodar minha aplicação. Um banco de dados que pode ser usado sem a necessidade de ser instalado é o Apache Derby (a Sun Microsystems utiliza esse mesmo banco de dados com o nome de JavaDB, não sei ao certo a relação entre eles), que é implementado 100% em Java, o que se torna uma excelente opção para aplicações portáveis que utilizam banco de dados e são feitas em Java.

Existem duas formas de se acessar um banco de dados no derby, são elas:

Conexão embarcada: realiza uma conexão diretamente no arquivo da base de dados, por esse motivo não é possível criar mais de uma conexão simultaneamente para a mesma base de dados.

Conexão via serviço de rede: mesmo utilizando o SGBD derby como um banco portável é possível inicializar o serviço de rede sem realizar uma instalação, isso pode ser feito diretamente pelo código, ou pela linha de comando de um terminal, seja (L)unix ou windows.

Requisitos

Os requisitos aqui apresentados se referem a este post, nada impede criar uma aplicação em outra linguagem e utilizar um drive de conexão para o derby.

* Sua aplicação deve ser feita utilizando Java;
* baixar o arquivo: db-derby-v.x.y.z-lib.zip
– derby, derbyclient, derbynet, derbyrun: obrigatórios para executar sua aplicação
– derbytools: jar para executar funções auxiliares do banco de dados, como por exemplo ver info do Derby
– derby_LOCALE_LANGUAGE: adicione sua linguagem para sua aplicação para que o derby possa mostrar as mensagem traduzidas
* Uma JVM instalada no computador cliente

Iniciando o banco de dados derby

Se você for utilizar a conexão embarcada não é necessário iniciar o Apache Derby, uma vez que esse modo não acessa via serviço. Caso contrário você pode iniciar o banco pela linha de comando, utilizando:
java -jar derbynet.jar start
Quando você inicializar o banco pela linha de comando o derby automaticamente irá procurar pela variável de ambiente DERBY_HOME que indica a pasta onde ficam guardados as bases de dados a serem acessadas, no linux você pode definir essa variável através do comando export DERBY_HOME=/home/diretorio, pelo windows, pode-se definir essa variável através Propriedades do Sistema->Avançado->Variáveis de ambiente.

Se preferir, você pode inicializar o banco derby diretamente pelo código de sua aplicação derby, basta inserir as seguintes linhas de código na sua aplicação:

try {
//Aqui você também pode utilizar um caminho relativo, porém lembre-se de que esse
//caminho inicia no mesmo diretório onde você iniciou a aplicação.
System.setProperty("derby.system.home", "/home/usuario/derby");
NetworkServerControlImpl networkServer = new NetworkServerControlImpl();
networkServer.start(new PrintWriter(System.out));
System.out.println("Conectado ao banco de dados.");
} catch (Exception ex) {
System.out.println("Não conseguiu conectar no banco de dados.");
}

Lembre-se de adicionar ao classpath os arquivos jar necessários para sua aplicação.

Caso a variável de ambiente DERBY_HOME não esteja definida (vale tanto para a inicialização via código quanto para a inicialização via terminal) o derby irá procurar automaticamente por base de dados localizadas na pasta de onde você iniciou a aplicação ou executou o comando de iniciar o derby.

Links

Site oficial: http://db.apache.org/derby/
Manual: http://db.apache.org/derby/manuals/index.html
Download: http://db.apache.org/derby/derby_downloads.html#Latest+Official+Release