Java/JPA: evitando null no Mapeamento de Banco


Olá Pessoal

Várias vezes já passei por várias dificuldades no mapeamento de banco de dados em Java, usando tanto Hibernate puro como JPA. Os maiores problemas não são com o uso das anotações ou configurações em XML, pois estas estão documentadas. Os maiores problemas são com pequenos detalhes que podem sair causando NullPointerExceptions a varrer. E depois que apareceu o erro, fica difícil depurar, pois o maior problema desse tipo de exceção é que ela é disparada onde deveria haver um objeto não nulo, e não onde se deveria colocar o valor não nulo no objeto enviado. É quase o problema do ovo ou a galinha.

O problema do null

O Java tem suporte a referência nula, o famigerado null. Isso quer dizer que quando você escreve String str = null, você está dizendo guarda o nome str pra quando eu quiser uma String, mas não coloca nada lá ainda. O problema é que quando você for passar a variável str para algum método, ele terá que testar se o valor é null, caso contrário ele lançará uma NullPointerException quando tentar acessar qualquer método da classe String. O problema vai parecer ser o método que usa a variável, ao invés do método que criou a variável. O Java tem suporte a validação de parâmetros de métodos usando anotações (ex.: @NotNull), mas isso não impede de criar uma variável com valor nulo, só faz a validação dos parâmetros de entrada de um método em que a String vai ser usada no futuro.

Voltemos ao caso String str = null. Pense no seguinte: o que significa null? Em princípio, que a String str está vazia. Não seria o mesmo que String str = ""? Caso a resposta seja sim (e é sim!), ao invés de testar em tudo que é lugar se a String str é null, podemos simplesmente usar o método isEmpty().

Colocando agora com mais código:

// usando null
String str = null;

///

void metodoQueUsaString(String str){
    if(str != null) {
        // faz o processamento
    } else {
       // reporta algum tipo de erro, ou não faz nada
    }
}
// sem usar null
String str = "";

void metodoQueUsaString(String str){
        // faz o processamento
 }

Viu como é mais simples? (SIM!)

Sempre que possível, garanta um retorno não nulo!

O que isso tem com mapeamento de banco?

Simples! JPA/Hibernate retornam null sempre que não há resultado encontrado! E agora?

Primeiramente vamos avaliar o seguinte: sempre (quase sempre) que usamos JPA e Hibernate, usamos algum tipo de padrão, como o DAO, para encapsular as chamadas ao JPA/Hibernate e agrupar os métodos relevantes para cada tipo de entidade mapeada. Por exemplo:

class MinhaEntidadeDAO {

    public MinhaEntidade buscaPorId(Number id){
        // busca uma entidade no banco por id
    }

    public List<MinhaEntidade> listar(){
        // busca todas as entidades no banco
    }

    public void salvar(MinhaEntidade me){
        // salva/atualizar no banco
    }
}

O problema é que estamos tão acostumados com o null que não percebemos que ele permeia tudo e muitas vezes sem necessidade. Vejamos:

buscaPorId:
o método retorna a entidade encontrada ou null
listar:
o método retorna a lista de entidades, ou null

Para tudo!!!!

listar()

Primeiro: por que causa, motivo, razão ou circunstância retornar null no método listar ao invés de retornar uma lista vazia? Retornando uma lista vazia não teremos NullPointerException. Mas como saber se existe alguma coisa lá? Simples: isEmpty().

Imagine um método em que não importe quantos resultados você recupera, mas o que importa é que uma operação qualquer seja efetuada em todos os valores retornados. Por exemplo, tem um campo com valor 1 na minha tabela, e quero que todos passem para 2:


@Entity @Table(name = "minha_entidade") class MinhaEntidade { // mepeamento de id omitido por simplicidade int valor; int getValor(){ return valor; } void setValor(int valor){ this.valor = valor } }

Para mudar todos os valores de 1 para 2:


MinhaEntidadeDAO dao = new MinhaEntidadeDAO(); for(MinhaEntidade me: dao.listar()){ if(me.getValor() == 1){ me.setValor(2); dao.salvar(me); } }

Neste caso acima, se o méodo listar retornar null só porque não tem nenhum registro, então vou ter que adicionar uma checagem de nulo:

List<MinhaEntidade> lista = dao.listar();
if (lista != null){
     // código anterior
}

Teremos então um aumento de complexidade! Mas se ao invés de retornar nulo, eu retorna uma lista vazia, não preciso mais checar por nulo, e simplifico meu código.

Mas se eu precisar de uma lista NÃO vazia? Somente nestes casos testo com lista.isEmpty() e pronto!

List<MinhaEntidade> lista = dao.listar();
if (!lista.isEmpty()){
    // código que exige lista com pelo menos 1 elemento
}

buscaPorId(Number id)

Segundo: como resolver o problema de quando não encontrar um valor para uma busca por 1 elemento, como o buscaPorId, retornar algo diferente de null?

Podemos criar uma interface, por exemplo EntidadePersistida, com métodos comuns a todas as entidades:


interface EntidadePersistida { Number getId(); // minhas entidades sempre tem id numérica (int ou long) boolean isNull(); }

Ao implementar o mapeamento, devemos implementar a interface EntidadePersistida e preencher o método isNull com uma verificação de campos que indique que a entidade não está preenchida. Isso pode ser feito até por uma variável booleana!

class MinhaEntidade implements EntidadePersistida {
    // algumas omissões por simplicidade

    boolean nula = false;

    MinhaEntidade(boolean nula){
        this.nula = nula;
    }

    boolean isNull(){
        return nula;
    }
}

ou

class MinhaEntidade implements EntidadePersistida {
    // algumas omissões por simplicidade

    boolean isNull(){
        // codigo que avalia campos para ver se é nulo
        // ATENÇÃO: id da entidade é nula sempre que ela ainda não foi salva, o que não serve para o teste de isNull!!!!!
    }
}

Dessa forma, sempre que fosse ser retornado null para uma entidade, retornamos uma entidade que retorne true para o método isNull, evitando o NullPointerException.

Pode parecer mais trabalhoso criar assim, mas é muito pior tem que encontrar o maldito código que retorna nulo e não deveria, ou checar em tudo que é lugar se o maldito valor é null!!!!!!!

Vida longa e próspera!

Deixe um comentário