terça-feira, 10 de outubro de 2017

JPA - Quando e como usar uma Entidade ou um VO(DTO) na minha aplicação?


Bem, para começarmos vamos definir algumas nomenclaturas usadas em padrões de projeto de desenvolvimento.

Java Bean – Uma classe Java simples, com propriedades privadas, métodos de acesso, construtor padrão. Daí saem ideias como Bean CDI, ManagedBean JSF, etc. Também são chamados de POJO (Plain Old Java Object), que quer dizer nada mais nada menos que “bom e” Velho Objeto Java Padrão.

PO ou Entidade – Um bean Java que pode ser persistido no banco de dados. Também conhecido como PO (Persistent Object). Nomenclaturas muito usadas quando se trabalha com frameworks de persistência ORM como o Hibernate.  Trata-se de uma classe Java com propriedades privadas, métodos de acesso, construtor padrão, e métodos de identidade como equals e hashCode. Também devem implementar a interface Serializable. Podem ter sobrecarga de construtores e alguns métodos específicos da classe em alguns casos. Suas propriedades são persistidas no banco de dados.

VO (Value Object) – São objetos Java usados para guardar valores em memória, geralmente para usar na camada de visão, não sendo persistidos no banco de dados. Quando se há a necessidade de persistir parte de suas propriedades ou todas elas, elas são passadas para uma instancia de uma Entidade. São muito semelhantes (se não iguais) ao uso dos DTO’s (Data Transfer Object). Eu particularmente considero exatamente a mesma coisa.

      Dada a devida explicação, vemos que basicamente as Entidades tem suas propriedades persistidas no banco enquanto os VO’s são usados para exibir dados na camada de visão ou coisas semelhantes, desde que não envolva persistência.

A primeira pergunta é: Por quê?

Se eu posso usar o objeto que é a própria entidade para exibir dados na camada de visão ou fazer qualquer outra coisa, por que usar um VO ou DTO?

Basicamente por 2 motivos:

1.        A Entidade é engessada. Uma Entidade se comporta como uma estrutura engessada na hora das consultas, dificultando queries nativas, e impedindo de mesclar propriedades de Entidades diferentes. Te obriga a montar as Entidades que compõe a Entidade principal, o que pode trazer um grande encadeamento dependendo da complexidade das consultas.

2.        Melhora de performance. Entidade pode ser muito grande e pesada, e você pode estar precisando apenas de 5 ou 10 propriedades, que podem inclusive serem montadas em Tipos Nativos do Java, diminuindo o grau de encadeamento destas, o que melhora a performance.

A segunda é: Quando usar VO e Quando usar Entidade?

Use Entidades quando os dados forem ser persistidos ou quando de fato você for carregar todas ou a maioria das propriedades, ou a entidade for simples.

Use VOs quando for criar Relatórios, disponibilizar os dados em um WebService, quando quiser juntar dados de diferentes Entidades, etc.; e quando precisar fazer consultas nativas complexas.

A terceira e mais importante é: como usar?

Existem algumas formas de preencher o VO a partir de uma consulta, e isso muda dependendo da forma como a consulta é feita ou o VO é construído. Por exemplo, se a consulta é nativa ou não, se o VO está ou não anotado com anotações do JPA, se um construtor é passado dentro da consulta, etc. Na tabela abaixo vou listar as 3 formas, depois irei dar um exemplo de cada uma delas:

VO
Como preencher na consulta SQL
VO com anotações JPA
Consulta nativa passando a classe para o EntityManager. Ex: “entityManager.createQuery(consulta, classeVO.class)”
VO sem anotações JPA
Consulta JPQL usando o construtor do VO na consulta, sem passar a classe para o entityManager.
VO sem anotações JPA
Consulta nativa sem passar a classe do VO para o entityManager e sem usar construtor do VO na consulta, porém preenchendo o VO percorrendo o resultList que será uma lista de Object onde cada Object desse é um Array de Object (Object[]) e em cada posição do array se encontra uma propriedade retornada na consulta.


Vamos analisar primeiro um VO com anotações do JPA e a consulta nativa. Segue o VO:

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class VeiculoVO implements Serializable
{
     private static final long serialVersionUID = -5022603734460617635L;

     @Id
     @Column(name = "SEQ_VEICULO")
     private Long seqVeiculo;

     @Column(name = "SEQ_LICENCA")
     private Long seqLicenca;

     @Column(name = "VEI_DSC_PLACA")
     private String placa;

     @Column(name = "COR")
     private String cor;

     @Column(name = "MODELO")
     private String modelo;

     public VeiculoVO()
     {

     }

     public VeiculoVO(final Long seqVeiculo, final Long seqLicenca, final String placa, final String cor, final String modelo)
     {
           super();
           this.seqVeiculo = seqVeiculo;
           this.seqLicenca = seqLicenca;
           this.placa = placa;
           this.cor = cor;
           this.modelo = modelo;
     }
    
     // demais getters and setters, equals e hashCode
}

Note que por ser um VO ele pode conter campos de diferentes Entidades, uma vez que seu papel é justamente ser um auxiliar. Apesar de anotado com as anotações do JPA, não informei nenhuma tabela justamente porque ele não tem esse vínculo no banco de dados.

Os nomes dado aos campos na anotação @Column devem ser os mesmos retornados na consulta!

Vamos ao exemplo de uso: uma consulta nativa onde a classe é passada para o EntityManager junto com o SQL no momento da criação da query:

final StringBuilder sb = new StringBuilder();

sb.append(" SELECT "
+ "     (SELECT LIV.SEQ_LICENCA_AMBIENTAL FROM EMT_EMPRESA_TRANSBORDO EMT "
+ "           INNER JOIN LIA_LICENCA_VEICULO LIV ON LIV.EMT_SEQ_EMPRESA = EMT.EMT_SEQ_EMPRESA "
+ "           INNER JOIN VEI_VEICULO VEI2 ON VEI2.LIV_SEQ_LICENCA_VEICULO = LIV.LIA_SEQ_LICENCA_VEICULO "
+ "           WHERE VEI2.VEI_DSC_PLACA = VEI.VEI_DSC_PLACA) AS SEQ_LICENCA, "
+ "     VEI.VEI_SEQ_VEICULO AS SEQ_VEICULO, VEI.VEI_DSC_PLACA AS VEI_DSC_PLACA, "
+ "     VEI.VEI_DSC_COR AS COR, VEI.VEI_DSC_MODELO AS MODELO "
+ " FROM EMT_EMPRESA_TRANSBORDO EMT "
+ " INNER JOIN LIV_LICENCA_VEICULO LIV ON LIA.EMT_SEQ_EMPRESA = EMT.EMT_SEQ_EMPRESA "
+ " INNER JOIN VEI_VEICULO VEI ON VEI.SEQ_LICENCA_VEICULO = LIV.SEQ_LICENCA_VEICULO "
+ " INNER JOIN CON_CONTRATO CON ON EMT.CON_SEQ_CONTRATO = CON.CON_SEQ_CONTRATO "
+ " INNER JOIN CLI_CLIENTE CLI ON CON.CLI_SEQ_CLIENTE = CLI.CLI_SEQ_CLIENTE ");

final Query query = entityManager.createNativeQuery(sb.toString(),
                     VeiculoVO.class);
    
return query.getResultList();
    
Na Entidade Veiculo, a parte que está grifada de vermelho na verdade é uma Entidade chamada de Licenca, que guarda dados referentes a licença do veículo, mas no VO VeiculoVO só era necessário carregar o ID dessa entidade.

Agora vamos para um segundo caso, que não tem as anotações do JPA no VO, mas usamos JPQL:

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

public class VeiculoVO implements Serializable
{
     private static final long serialVersionUID = -5022603734460617635L;

     private Long seqVeiculo;

     private Long licSeqLicenca;

     private String placa;

     private String cor;

     private String modelo;

     public VeiculoVO()
     {

     }

public VeiculoVO(final Long seqVeiculo, final String placa, final Long seqLicenca)
{
super();
this.seqVeiculo = seqVeiculo;
this.placa = placa;
this.seqLicenca = seqLicenca;
}

     public VeiculoVO(final Long seqVeiculo, final Long seqLicenca, final String placa, final String cor, final String modelo)
     {
           super();
           this.seqVeiculo = seqVeiculo;
           this.seqLicenca = seqLicenca;
           this.placa = placa;
           this.cor = cor;
           this.modelo = modelo;
     }
    
     // demais getters and setters, equals e hashCode
}

Note que agora o VO não tem anotações JPA e nós queremos trazer apenas 3 campos dele dessa vez: a placa e o sequencial do veículo e o sequencial da licença. Para isso adicionamos o construtor adequado.

Vamos a consulta JPQL:

final StringBuilder sb = new StringBuilder();

sb.append(" SELECT NEW br.com.meuprojeto.modelo.VeiculoVO(
vei.seqVeiculo, vei.placaVeiculo, liv.licencaVeiculo) FROM Veiculo vei INNER JOIN vei.licencaVeiculo liv
");

final Query query = entityManager.createQuery(sb.toString());
    
return query.getResultList();

Agora vamos para o terceiro caso, onde não temos as anotações do JPA no VO, e usamos uma consulta nativa, observe que assumimos que o VO possui os getters and setters necessários:

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

public class VeiculoVO implements Serializable
{
     private static final long serialVersionUID = -5022603734460617635L;

     private Long seqVeiculo;

     private Long licSeqLicenca;

     private String placa;

     private String cor;

     private String modelo;

     public VeiculoVO()
     {

     }

     // construtores
    
     // demais getters and setters, equals e hashCode
}

Vamos a consulta SQL:

final StringBuilder sb = new StringBuilder();

sb.append(" SELECT VEI.VEI_SEQ_VEICULO, "
+ " VEI.VEI_DSC_PLACA FROM EMT_EMPRESA_TRANSBORDO EMT "
+ " INNER JOIN LIV_LICENCA_VEICULO LIA ON LIV.EMT_SEQ_EMPRESA = EMT.EMT_SEQ_EMPRESA "
+ " INNER JOIN VEI_VEICULO VEI ON VEI.SEQ_LICENCA_VEICULO = LIV.SEQ_LICENCA_VEICULO ");

final Query query = entityManager.createNativeQuery(sb.toString());
    
final List<?> resultList = query.getResultList();

Após a consulta SQL, teremos o resultList que é uma lista de Object onde cada Object desse é um Array de Object (Object[]) e em cada posição do array se encontra uma propriedade retornada na consulta. temos que trabalhar esses dados percorrendo a lista, recuperando os dados e colocando no VO:

final List<VeiculoVO> lstVeiculoVO = new ArrayList<>();

if ( resultList != null && resultList.size() > 0 ) {

for ( final Object value : resultList ) {

final VeiculoVO auxiliarVO = new VeiculoVO();
final Object[] result = (Object[]) value;

auxiliarVO.setSeqVeiculo( ( (BigDecimal) result[0] ).longValue() );

auxiliarVO.setPlaca( ( (String) result[1] ).toString() );
               
                lstVeiculoVO.add( auxiliarVO );
            }
        }

return lstVeiculoVO;


Pronto, essas são as formas de se trabalhar com VO no JPA. Espero que faça bom proveito!

4 comentários:

  1. Muito bacana suas explicações.
    Eu preciso aprender como popular um "VeiculoVo" que tem como atributo um "List" e recebe no construtor, mas já tentei de tudo e não consegui. Pode me dar um exemplo. Agradeço muito...Valeu!!!

    ResponderExcluir
    Respostas
    1. Que bom que gostou! Não da pra você carregar a lista na consulta principal se ela for nativa. No relacionamento um para muitos quando o JPA faz isso pra você ele faz a carga da lista e depois seta na seu bean, quando você faz manualmente, tem que fazer a consulta da lista separada e depois setar no seu bean. Usando consulta JPQL você pode usar FETCH JOIN. Também pode usar o Hibernate.initialize(List) passando sua lista após ter o objeto principal carregado.

      Excluir