Javafree

Prevayler com SQL, apresentando JoSQL

Publicado por Tutoriais Admin em 19/04/2012 - 13.199 visualizações

De agora em diante esqueça a frase: Prevayler não tem suporte a SQL . Este artigo mostrará como integrar os frameworks Prevayler e JoSQL , em uma arquitetura que almeja executar instruções SQL sobre os objetos Java persistidos pelo Prevayler. Não é intuito do artigo mostrar como o Prevayler ou o JoSQL funcionam, portanto, é recomendado ler outros documentos para complementar e esclarescer este artigo. Aqui no JavaFree estão publicados 3 artigos sobre o prevayler: Parte 1 , Parte 2 e Parte 3

Os frameworks envolvidos

O Prevayler é um framework para prevalência de objetos, ou seja, é um framework de persistência mas que garate que os objetos estejam sempre disponíveis na memória, idependente da estrutura que o usuário criar. O Prevayler é o projeto mais defendido pelos puristas em OO, pois não existem estruturas relacionais quando este é usado, ou seja, não há um banco de dados.

Por ser totalmente OO, o Prevayler é alvo de muita insegurança nos desenvolvedores que utilizam banco de dados. Como se não bastasse o requisito de permanecer em memória, o Prevayler não possui suporte a famosa Structured Query Language (SQL), criando um desafio quando selects mais complexas são necessárias. Em teoria toda a consulta seria construída na mão, varrendo os objetos com loops (for, while e do-while), porém algumas consultas podem ficar grandes e baixar a produtividade dos desenvolvedores. Mesmo assim não faz parte dos planos do Prevayler Team adicionar algum suporte a SQL, mas isso não quer dizer, que nós não podemos criar.

A versão que usaremos é a mais atual, o Prevayler 2.02.006, que pode ser encontrado na página de download do Prevayler. Baixe o zip, descompacte, crie um novo projeto na sua IDE preferida e adicione o prevayler-2.02.006.jar no projeto.

O JoSQL (SQL for Java Objects) é um framework que tem como objetivo executar SQLs sobre instâncias de classes Java. Qualquer instância pode ser utilizada e qualquer atributo ou função pode ser acessado pela SQL, além das funções nativas como SUM, AVG, COUNT e estruturas de GROUP BY. Basta que você indique a SQL a executar e em quais objetos deseja que o JOSQL faça o filtro.

O JoSQL é o primeiro framework que possui este tipo de objetivo. O projeto apadrinhado pelo JavaFree chamado SnailDB segue no mesmo caminho, mas com um foco maior sobre as aplicações legadas. O JoSQL possui um manual bem completo na sua página .

A versão que utilizaremos do JoSQL é a 1.0 stable, lançada recentemente pela equipe do projeto. Pode ser baixada através desta url . Após o download, descompacte e coloque o JoSQL-1.0.jar e a sua dependência gentlyWEB-utils-1.1.jar no classpath do seu projeto. A dependência encontra-se dentro do diretório 3rd-party-jars . Feito isso estamos prontos para criar o nosso exemplo.

Mãos à massa

Unir o Prevayler e o JoSQL é uma tarefa muito simples. Porém vale lembrar que o desenvolvedor deve tomar cuidado para não misturar mundos relacionais com orientação a objetos. O custo desse erro em projetos maiores é enorme. Outro aviso importante é que, por mais prático que seja a utilização do JoSQL, as queries serão montadas em variáveis String, ou seja, não são compiladas pelo javac e, com isso, você só encontrará erros na SQL em tempo de execução. A sugestão é usar testes unitários para cada SQL, isso garantirá que, ao alterar uma classe, os testes encontrem as SQLs que também devem ser alteradas.

O exemplo adiante utiliza as mesmas classes criadas nos artigos do Prevayler aqui no JavaFree. O objetivo da aplicação continua o mesmo: armazenar uma lista de pessoas. Porém, neste artigo, consultas mais audaciosas serão feitas com a classe Pessoa , que sofreu alteração na sua estrutura: Novos atributos foram adicionados e as novas construções do Java 5.0 estão sendo utilizadas. É necessária uma JRE com suporte a versão 5.0 para implemementar este exemplo.

Todas as classes listadas abaixo estão dentro do pacote: " org.josqlprev ". Talvez seja necessário criá-lo separadamente na sua IDE.

Observem a classe Pessoa , abaixo:


package org.josqlprev;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Pessoa implements Serializable {
    private static final long serialVersionUID = 1L;
   
    private String nome;
    private String sobrenome;
    private int idade;
    private Date nascimento;
   
    public Pessoa(String nome, String sobrenome, int idade, Date nascimento) {
        super();
        this.idade = idade;
        this.nascimento = nascimento;
        this.nome = nome;
        this.sobrenome = sobrenome;
    }
   
    public int getIdade()           { return this.idade;        }
    public Date getNascimento()     { return this.nascimento;   }
    public String getNome()         { return this.nome;         }
    public String getSobrenome()    { return this.sobrenome;    }
   
    public void setIdade(int idade)             { this.idade = idade;           }
    public void setNascimento(Date nascimento)  { this.nascimento = nascimento; }
    public void setNome(String nome)            { this.nome = nome;             }
    public void setSobrenome(String sobrenome)  { this.sobrenome = sobrenome;   }
   
    public String toString() {
        SimpleDateFormat formatador = new SimpleDateFormat("EEEEEEEE, 'dia' dd 'de' MMMMMM 'de' yyyy");
       
        return nome + " " + sobrenome + " com " + idade + " anos e nascimento em " + formatador.format(nascimento)
    }
}

Esta é a nossa classe de dados. Os atributos nome, sobrenome, idade e nascimento foram adicionados na estrutura pois representam tipos diferentes de dados. Desta maneira as consultas podem ser mais interessantes. Reparem que ela já está preparada para ser salva pelo Prevayler.

Abaixo, a classe ListaPessoas que atua como o banco de dados, ou seja, o PrevalentSystem do Prevayler.


package org.josqlprev;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.josql.Query;
import org.josql.QueryExecutionException;
import org.josql.QueryParseException;

public class ListaPessoas implements Serializable {
    private static final long serialVersionUID = 1L;

    private ArrayList<Pessoa> listaPessoas = new ArrayList<Pessoa>();

    public void add(Pessoa pes) {
        listaPessoas.add(pes);
    }

    public void remove(Pessoa pes) {
        listaPessoas.remove(pes);
    }

    public Pessoa get(int i) {
        return listaPessoas.get(i);
    }

    public int size() {
        return listaPessoas.size();
    }

    public List<Pessoa> getAll()  {
        return listaPessoas;
    }
}

Esta classe armazena uma lista da classe Pessoa , possuindo métodos básicos para consulta. Também já está pronta para ser persistida pelo Prevayler.

A seguir é criado uma classe transação para que o Prevayler possa persistir os dados. Trata-se do cadastro de Pessoa. Esta classe é utilizada a cada nova pessoa digitada pelo usuário.


package org.josqlprev;

import java.util.Date;
import org.prevayler.Transaction;

public class AdicionaPessoa implements Transaction {
  
    private static final long serialVersionUID = 1L;

    private String nome;
    private String sobrenome;
    private int idade;
    private Date nascimento ;

    public AdicionaPessoa() {
        super();
    }
   
    public AdicionaPessoa(String nome, String sobrenome, int idade, Date nascimento) {
        super();
        this.idade = idade;
        this.nascimento = nascimento;
        this.nome = nome;
        this.sobrenome = sobrenome;
    }


    public void executeOn(Object system, Date arg1) {
      ((ListaPessoas)system).add(new Pessoa(nome, sobrenome, idade, nascimento));
    }

Agora que já temos todas as classes de dados e as transações implementadas, vamos criar uma classe Main e verificar se o Prevayler já está funcionando. O Main em questão está abaixo:


package org.josqlprev;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;

import org.josql.QueryExecutionException;
import org.josql.QueryParseException;
import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;

public class Main {
    private Prevayler prevayler;
   
    /* Processo principal */
    public Main() {
        println("Iniciando Prevayler...");

        iniciarPrevayler();

        // Cadastro de pessoas
       
        String nome = ler("Digite o nome da pessoa ou FIM para sair: ");

        while (!nome.equals("FIM")) {
            adicionarPessoa(nome);
           
            nome = ler("Digite o nome da pessoa ou FIM para sair: ");
        }

        // Consulta de pessoas
        ListaPessoas lista = ((ListaPessoasprevayler.prevalentSystem());
       
        print(lista.getAll()"Imprimindo pessoas persistidas.");
    }
   
    public void print(List<Pessoa> lista, String titulo) {
        println("\n-------------------------------");
        println(titulo);
        println("-------------------------------");
       
        for (Pessoa p : lista) {
            println(p.toString());
        }  
    }
   
    /** Imprime o texto e le uma informação em String */  
    public String ler(String texto) {
        print(texto);
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            return reader.readLine();
        catch (IOException e1) {
            return "FIM";
        }
    }
   
    /* Escreve na saida padrão */
    public void print(String texto) {
        System.out.print(texto);
    }
   
    /* Escreve na saida padrão e pula linha */
    public void println(String texto) {
        System.out.println(texto);
    }
   
    /** Inicia o Prevayler */
    public void iniciarPrevayler() {
        try {
            prevayler = PrevaylerFactory.createPrevayler(new ListaPessoas());
        catch (IOException e) {
            e.printStackTrace();
            System.exit(0);
       catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.exit(0);
        }
    }
   
    /** Le o restante dos dados da pessoa e adiciona no prevayler **/
    public void adicionarPessoa(String nome) {
        String sobrenome = ler("Sobrenome: ");
        int idade = Integer.parseInt(ler("Idade: "));
        Date nascimento;
        try {
            nascimento = DateFormat.getDateInstance().parse(ler("Nascimento: "));
        catch (ParseException e) {
            nascimento = new Date();
        }
       
        try {
            prevayler.execute(new AdicionaPessoa(nome, sobrenome, idade, nascimento));
        catch (Exception e1) {
            e1.printStackTrace();
        }

        println("Pessoa armazenada.");
    }
   
    public static void main(String[] args) {
        new Main();
    }
}

Decifrem o código antes de continuarmos. Vocês verão que este Main nada mais é do que uma interface em modo caracter do Java. Ele colhe informações das pessoas e persiste com o Prevayler (Não esqueça de colocar o jar do Prevayler no classpath).

Mas, até agora não temos nada de SQL, certo? Então vamos adicionar alguma lógica de negócio a essa aplicação. Que tal construirmos um filtro pedindo todas as pessoas com idade menor que 21 anos? Todos os filtros serão criados na classe ListaPessoas , afinal, é ela que armazena todas as pessoas e é ela a reponsável por catalogá-las. Vamos lá.

Dentro da classe ListaPessoa crie este método:

    public List<Pessoa> execute(String sqlthrows QueryExecutionException, QueryParseException {
        Query q = new Query();

        q.parse(sql);
      
        return (List<Pessoa>q.execute(listaPessoas).getResults();
}   

Este método cria uma instância de Query , do JoSQL. Este objeto faz o parse em uma SQL e executa sobre uma lista, conforme pode ser visto na função. Como estaremos sempre tratando de uma lista de pessoas, ela já retorna o tipo de dado correto para facilitar e evitar erros de cast . O throws é necessário pois estaremos repassando a responsabilidade sobre a SQL para o método externo a esse. Então, o método que invocar esta função deverá tratar estes possíveis erros.

Para criar o nosso filtro de menores de 21, basta criar este método na mesma classe ListaPessoa :


    public List<Pessoa> getMenoresDe(int idadethrows QueryExecutionException, QueryParseException {
        return execute("select * from org.josqlprev.Pessoa where idade < " + idade);
    }

Repare que a select é idêntica a utilizada na maioria dos bancos de dados. A única diferença é que ao invés de ter uma tabela no from , teremos uma classe do Java. No nosso caso: org.josqlprev.Pessoa . Como vcs podem perceber não é difícil montar SQLs sobre os objetos, pois a grande maioria já lidou com esses comandos.

Para fazer funcionar, basta adicionar este trecho de código no final do construtor da classe Main .

       try {
            print(lista.getMenoresDe(21)"Imprimindo menores de 21.");
        catch (QueryExecutionException e) {
            e.printStackTrace();
        catch (QueryParseException e) {
            e.printStackTrace();
        }

Pronto! Temos o filtro para menores de 21 anos. Teste! A seguir estão outras SQLs que podem ser executadas sobre a classe pessoa. Coloque-as dentro da classe ListaPessoas . A primeira irá retornar todos os registros com sobrenome igual a uma String, a segunda irá retornar todas as pessoas que nasceram num determinado dia da semana. E a terceira retornará todas as pessoas que nasceram em 2005, observe o uso do BETWEEN para a comparação das datas.

    public List<Pessoa> getSobrenomeIgual(String sobthrows QueryExecutionException, QueryParseException {
        return execute("select * from org.josqlprev.Pessoa where sobrenome = '" + sob+ "'");
    }

    public List<Pessoa> getNascimentoNa(int diathrows QueryExecutionException, QueryParseException {
        return execute("select * from org.josqlprev.Pessoa where getDiaDaSemana = " + dia);
    }
   
    public List<Pessoa> getNascimentoEm2005() throws QueryExecutionException, QueryParseException {
        return execute("select * from org.josqlprev.Pessoa where nascimento BETWEEN toDate ('01/Jan/2005') AND toDate ('31/Dez/2005')");
    }

Para o select de dia da semana funcionar, coloque está função na classe Pessoa . O JoSQL trabalha com métodos e atributos dos objetos, mas como é impossível saber o dia da semana com uma instância de Date, cria-se um método que saiba como calcular isso e utilíza-se este método diretamente no select.


    public int getDiaDaSemana() {
        Calendar c = Calendar.getInstance();
       c.setTime(nascimento);
        return c.get(Calendar.DAY_OF_WEEK);
    }

O seguinte trecho deve ser colocado dentro do try, junto com a outra impressão de menores de 21 anos na classe Main.

            print(lista.getSobrenomeIgual("Pamplona")"Imprimindo todos da família Pamplona.");
            print(lista.getNascimentoNa(2)"Imprimindo todos que naceram na segunda.");
            print(lista.getNascimentoEm2005()"Imprimindo todos que naceram em 2005.")
;

Pronto! Agora você já tem várias consultas SQL sem ter um banco de dados e sobre uma coleção de objetos, não de tabelas. Lembre-se que você pode criar qualquer função na classe Pessoa e utilizá-la dentro da SQL. Esta função trabalharia como se fosse uma função do banco de dados, mas os benefícios são: é na mesma linguagem que todos trabalham, processa em memória e não em disco, compila junto com o teu código, pode ser avaliada por ferramentas conhecidas de métricas e testes unitários, e ainda não corre o risco de se perder em mudanças de versões nos clientes.

Espero que tenham gostado

comentários: 1

Tópicos Relacionados