quarta-feira, 10 de março de 2021

JPA 2.1 - Criteria Builder, evitando query adicional causada por FetchType Lazy (lazy-loading)

 Hoje durante a utilização da api criteria para consultas dinâmicas enquanto realizava um curso, me deparei com uma coisa bem chata, no qual eu possuo 3 beans.
  • Estado
  • Cidade
  • Usuário
Ocorria o seguinte inconveniente, devido essa consulta ser usada em filtros decidi usar o CriteriaBuilder pela facilidade em criar  dinamicamente as consultas, porem todas as vezes que eu realizava uma consulta sempre recebia duas consultas adicionais, uma para Cidade e outra para Estado. E antes que me perguntem sim! marquei como lazy essas relações e não desejo nem quero que sejam JoinType Eager sempre, porem ao usar o CriteriaBuilder ele ignora completamente e volta a usar o lazy-loading.

  
    select
        cliente0_.codigo as codigo1_2_,
        cliente0_.cpf_cnpj as cpf_cnpj2_2_,
        cliente0_.email as email3_2_,
        cliente0_.cep as cep4_2_,
        cliente0_.codigo_cidade as codigo_11_2_,
        cliente0_.complemento as compleme5_2_,
        cliente0_.logradouro as logradou6_2_,
        cliente0_.numero as numero7_2_,
        cliente0_.nome as nome8_2_,
        cliente0_.telefone as telefone9_2_,
        cliente0_.tipo_pessoa as tipo_pe10_2_ 
    from
        cliente cliente0_ 
    where
        1=1 limit ?;

    select
        cidade0_.codigo as codigo1_1_0_,
        cidade0_.codigo_estado as codigo_e3_1_0_,
        cidade0_.nome as nome2_1_0_ 
    from
        cidade cidade0_ 
    where
        cidade0_.codigo=?;

    select
        estado0_.codigo as codigo1_3_0_,
        estado0_.nome as nome2_3_0_,
        estado0_.sigla as sigla3_3_0_ 
    from
        estado estado0_ 
    where
        estado0_.codigo=?

O que de fato não é nem de longe ideal para um bom desempenho, mas então oque fazer ? será que existe uma forma de incorporar na primeira query as duas adicionais ? E a resposta é sim! basta fazer o fetch ,  nas relações que deseja evitar as consultas adicionais, mesma coisa que fazemos no HQL.

   Root cliente = querie.from(Cliente.class);           
            
   cliente.fetch(Cliente_.ENDERECO)//Representa o endereço 
          .fetch(Endereco_.CIDADE, JoinType.LEFT)//Realiza o Fetch de Cliente com Cidade
          .fetch(Cidade_.ESTADO, JoinType.LEFT);  //Realiza o Fetch da Cidade com estado
  
A partir de agora a consulta gerada é a seguinte.
  
    select
        cliente0_.codigo as codigo1_2_0_,
        cidade1_.codigo as codigo1_1_1_,
        estado2_.codigo as codigo1_3_2_,
        cliente0_.cpf_cnpj as cpf_cnpj2_2_0_,
        cliente0_.email as email3_2_0_,
        cliente0_.cep as cep4_2_0_,
        cliente0_.codigo_cidade as codigo_11_2_0_,
        cliente0_.complemento as compleme5_2_0_,
        cliente0_.logradouro as logradou6_2_0_,
        cliente0_.numero as numero7_2_0_,
        cliente0_.nome as nome8_2_0_,
        cliente0_.telefone as telefone9_2_0_,
        cliente0_.tipo_pessoa as tipo_pe10_2_0_,
        cidade1_.codigo_estado as codigo_e3_1_1_,
        cidade1_.nome as nome2_1_1_,
        estado2_.nome as nome2_3_2_,
        estado2_.sigla as sigla3_3_2_ 
    from
        cliente cliente0_ 
    left outer join
        cidade cidade1_ 
            on cliente0_.codigo_cidade=cidade1_.codigo 
    left outer join
        estado estado2_ 
            on cidade1_.codigo_estado=estado2_.codigo 
    where
        1=1 limit ?

Espero ter ajudado pessoas que assim como eu tiveram muitos problemas,  para conseguir realizar esse simples procedimento assim como era feito no HQL.

segunda-feira, 1 de junho de 2020

JasperReports Configurar PDF com Autor,Titulo, Assunto,Criador, Senha (Meta Data)

Seguindo com a serie de testes com jasperReports gerando o relatorios, notei que ao abrir o arquivo no navegador ele não tinha um título interessante, então fui procurar em como mudar o mesmo na pagina web, minha pesquisa me levou a descobrir uma série de configurações para a configuração do PDF alem de um simples titulo, assumindo que voce ja tem o arquivo jasperPrint disponivel temos o seguinte codigo.


JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, parametros,dataSource);            
String nomeArquivo =  "FICHA "+condutor.getNome().toUpperCase();
JRPdfExporter exportadorDePDF = new JRPdfExporter();
SimplePdfExporterConfiguration  configuracaoSimplesDoExportadorDePDF = new SimplePdfExporterConfiguration();
configuracaoSimplesDoExportadorDePDF.setMetadataAuthor("Denis Benjamim");
configuracaoSimplesDoExportadorDePDF.setMetadataTitle(nomeArquivo);
configuracaoSimplesDoExportadorDePDF.setMetadataSubject("Ficha Cadastral Condutor "+condutor.getNome());
configuracaoSimplesDoExportadorDePDF.setMetadataCreator("Transparency Logistica e Transportes ltda");
configuracaoSimplesDoExportadorDePDF.setMetadataKeywords("Ficha,Ficha Cadastral,Condutor,RG,CPF,CNH,habilitacao");;
configuracaoSimplesDoExportadorDePDF.set128BitKey(true);
configuracaoSimplesDoExportadorDePDF.setEncrypted(true);
configuracaoSimplesDoExportadorDePDF.setUserPassword("password");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
exportadorDePDF.setExporterInput(new SimpleExporterInput(jasperPrint));
exportadorDePDF.setExporterOutput(new SimpleOutputStreamExporterOutput(baos));
exportadorDePDF.setConfiguration(configuracaoSimplesDoExportadorDePDF);
exportadorDePDF.exportReport();         

return ResponseEntity.ok()                   
                .header(HttpHeaders.CONTENT_DISPOSITION,"inline; filename="+nomeArquivo+".pdf")
                .body(baos.toByteArray());

Resumindo estou configurando no pdf, o nome do autor, titulo, assunto, criador, palavras chaves, tipo de criptografía, informando que sera criptografado e terá senha, nada além dessas pequenas coisas.

setMetadataAuthor -> Pessoa que solicitou o pdf.
setMetadataTitle-> Titulo do pdf sera exibido na janela
setMetadataSubject ->Assunto do pdf.
setMetadataCreator-> Nome do software ou Empresa responsavel pela criação do PDF.
setMetadataKeywords-> Palavras chaves utilizadas para pesquisa do pdf.
set128BitKey-> Criptografía dos dados no pdf.
setEncrypted->Ativa criptografia.
setUserPassword-> Senha para acessar o pdf.

A classe responsável pela configuração é a SimplePdfExporterConfiguration, nela configuramos as informações conforme visto no codigo.

JRPdfExporter classe responsavel por exportar o pdf.
ByteArrayOutputStream sera usado para junto do JRPdfExporter para gerar o pdf.

setExporterInput -> Fluxo de entrada com jasperPrint.
setExporterOutput -> Fluxo de saida.
setConfiguration -> Arquivo de configuração que sera usado na geraçao do pdf.
exportReport - > Persiste a configuracao personalizada para ser enviada ao navegador.

No pdf temos a seguinte saida em suas propriedades.

domingo, 31 de maio de 2020

JasperReports JRBeanCollectionDataSource Java Bean Complexos

Vamos direto ao ponto. Quando usamos beans dentro de um JRBeanCollectionDataSource  e como no meu caso apenas linquei a pasta do projeto dentro do Jaspersoft Studio, quando temos um objeto diferente dos padrões do JRE, costumo ter um problema na hora de gerar vamos ver o caso:

Tenho uma Class Chamada Condutor dentro dela tenho vários atributos dentre eles alguns são classes como CidadeIBGE, EstadoIBGE e alguns sao Enuns Status,TipoCondutor, conforme imagem:


É importante ser notado que foi detectado corretamente o Class Type, mas mesmo assim vamos acabar esbarrando no problema das seguintes exceçoes NoClassDefFoundError e ClassNotFoundException durante a tentativa de geraçao do relatório com os dados do bean.

Os Bens selecionados tem os seguintes atributos:

Class CidadeIBGE
Class CidadeIBGE

Class EstadoIBGE

Enum Status

Vou usar o enum como o exemplo pois os demais serão exatamente iguais, tem um unico metodo que me é interessante que se refere a descrição, para que ele funcione corretamente sera necesario modificar algumas informacoes:
  1. Class Type Tem de ser o dado dado final, neste caso sera um java.lang.String.
  2. Em description vamos seguir o padrão Bean como se eu quisesse navegar no objeto ate seus atributos, neste caso ficaria situacao.descricao.
  3. Para realizar essas alterações basta dar dois cliques em uma das linhas que deseje editar


Se existe uma forma  de acessar as propriedades dos beans eu nao consegui encontrar, em fim! fica a dica para quem precisar um dia

Spring Boot + JasperReports exportando relatorios para PDF

É hoje temos algumas coisas interessantes para falar dentre elas a dificuldade de encontrar um bom e interessante codigo para gerar um simples pdf a partir de um JasperReport junto com uma aplicação Spring Boot.

Durante a pesquisa encontrei codigos muito parecidos com este a baixo e sim! Ele funciona perfeitamente.

@GetMapping
public void gerarPDF(@RequestParam Map parametros, HttpServletResponse response) throws IOException,JRException {
       
 if(parametros == null)
  parametros = new HashMap<>();          
           
 InputStream jasperStream = this.getClass().getResourceAsStream("../../../../reports/CondutorFicha.jasper")
 JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(Arrays.asList(condutores.findByCodigo(5)));
 JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, parametros,dataSource);
        
 response.setContentType("application/pdf");
 response.setHeader("Content-Disposition", "inline; filename=Ficha.pdf");

 final OutputStream outputStream = response.getOutputStream();
 JasperExportManager.exportReportToPdfStream(jasperPrint, outputStream);            
       
}

Explicando em miúdos:
jasperStream -> Capturando o arquivo do relatório no meu classpath de modo hard( pois existe uma forma mais facil), na sua forma compilada .jasper vi em vários locais que pessoal compila na hora de exibir o relatorio, como nao tenho que trabalhar com múltiplos idiomas preferi trabalhar diretamente com o .jasper ao .jrxml.

dataSource -> linha seguinte criando um JRBeanCollectionDataSource com os BEANS devidamente populados no meu banco.

jasperPrint -> unificando o relatório com seus parametros e dados para daí poder gerar o pdf preenchido.

O objeto response esta informando no header da resposta que vamos devolver um pdf e informando que sera exibido no navegador com o nome de Ficha.pdf.

outputStream -> Capturando a Stream da resposta para ser usada no retorno ao navegador.

JasperExportManager -> tem uma serie de metodos uteis para exportar para uma serie de formatos distintos, que no caso informei qual o jasperprint e para qual saída de fluxo ele iria.

Então se este código funciona perfeitamente por que tentar fazer de outra forma ?.. Insatisfação, teimosia , seja la qual foi o motivo eu queria poder usar todas as partes que conheço do Spring Boot ate o dia de hoje, eis que decido deixar meu código desta forma:

 
@GetMapping(produces = {MediaType.APPLICATION_PDF_VALUE})
public @ResponseBody ResponseEntity gerarPDF(@RequestParam Map parametros){
 if (parametros == null)
     parametros = new HashMap<>();     
    
 try (InputStream jasperStream = applicationContext.getResource("classpath:reports/CondutorFicha.jasper").getInputStream()) {

     JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(Arrays.asList(condutores.findByCodigo(5)));
     JasperPrint jasperPrint = JasperFillManager.fillReport(jasperStream, parametros,dataSource);         
   
     return ResponseEntity.ok()
      .header(HttpHeaders.CONTENT_DISPOSITION,"inline; filename=Ficha.pdf")
      .body(JasperExportManager.exportReportToPdf(jasperPrint));
 }catch(JRException | IOException exception ){
     exception.printStackTrace();
 }
 return ResponseEntity.badRequest().body("Nao foi possivel gerar o pdf");
}
 
Vou descrever  apenas as diferenças do com relação ao primeiro código. Começando que agora ele tem um retorno diferente do primeiro que era um void teremos como retorno um ResponseEntity

@GetMapping -> Agora tem um argumento informando que será produzido um PDF, descartando a configuracao no Header.

@ResponseBody -> Indica que o valor de retorno deve ser vinculado ao corpo da resposta

jasperStream -> é instanciado pelo ApplicationContext do Spring de uma forma mais simples comparando com a anterior na minha opinião. Além de ter usado um TRY Resources para abrir e fechar o fluxo

ResponseEntity - > Builder para a resposta como um HttpServletResponse, nele informei uma parte do header anterior, e no body passei como parâmetro um byte[] pois é isso que este método estático do JasperExportManager nos devolve. Além de ter um pequeno tratamento caso não seja possível gerar o pdf por algum motivo.

sábado, 30 de maio de 2020

Jaspersoft Studio + Lombok Instalação


Devo dizer que foi uma tarefa bem chata de se fazer ainda mais que o lombok no ubuntu não conseguiu reconhecer nem o Eclipse comum quem dirá a versão modificada que é o Jasper Studio nossa maravilhosa IDE para criação de relatórios das Jaspersoft.

Problema: Não instala de forma automática.


Necessidade: devido estar usando lombok no projeto se faz necessário que a IDE de relatórios também seja capaz de compilar os beans gerando assim setters e getters usando as annotations do lombok.

Soluçao: Instalar manualmente. Após encontrar o diretório de instalação do seu Jaspersoft Studio dentro do sistema operacional no seu computador, que no meu caso estava no caminho "/opt/tibco/TIB_js-studiocomm_6.12.2"(Ubuntu 16.04).

Vamos seguir a complicada explicação descrita dentro do proprio jar clicando em Show me what this installer will do to my IDE installation.


Considerações nao teremos dentro da pasta de instalação do Jaspersoft Studio um arquivo chamado Eclipse.ini nas sim Jaspersoft Studio.ini, como pode ser visto a seguir:


Copie o arquivo jar do lombok para dentro deste diretório e caso não tenha permissão de escrever, como eu basta conceder essa permissão pelo terminal  :x.

Após o arquivo estar no diretório abra o arquivo .ini e adicione no final do arquivo o caminho para o jar  do lombok lembrando se seu jar tem de estar com o mesmo nome que você informar no arquivo de configuração caso contrário vai acontecer um erro e a IDE não deve abrir.


basta colar a seguinte instrução " -javaagent:lombok.jar  " - Sem as aspas por favor rs. O arquivo deve ficar como acima, e para ter certeza conseguiu instalar corretamente basta abrir a IDE  > Help > About TIBCO Jaspersoft Studio e como pode notar na imagem temos descrito a versão do lombok instalada descrita nesta janela.



Agora e so compilar seu projeto e Correr para o abraço

terça-feira, 1 de outubro de 2019

JPA MAPEAMENTO MAP COM CHAVE ENUM E VALOR OBJECT


A baixo a descrição em código de como Mapear um objeto do tipo Map tendo como chave um Enum e valor uma Entidade que poderia ser qualquer outro Objeto

@Data @AllArgsConstructor @NoArgsConstructor @Builder
@Entity
@Table(schema = "gdf",name = "tb_VeiculoPneus")
 public
 class VeiculoPneus implements Serializable{

    @Id
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "CodVeiculo")
 private Veiculos codigoVeiculo;


    @OneToMany
    @JoinTable(name = "tb_VeiculoPosicaoPneu",schema = "gdf"
       ,uniqueConstraints = 
         @UniqueConstraint(name="UK_PNEU_VEICULOPOSICAOPNEU"
          ,columnNames={"CodPneu"})
       ,joinColumns= 
         @JoinColumn(name="CodVeiculoPneus"
          ,foreignKey= 
            @ForeignKey(name="FK_VEICULOPNEUS_VEICULOPOSICAOPNEU")
         )
       ,inverseJoinColumns=  
         @JoinColumn(name="CodPneu"
          ,foreignKey= 
            @ForeignKey(name="FK_PNEU_VEICULOSPOSICAOPNEU")
         ) 
    )
    @MapKeyEnumerated(EnumType.STRING)
    @MapKeyClass(PosicaoPneu.class)
    @MapKeyColumn(name ="CodPosicao")
 private Map<PosicaoPneu,Pneu> pneus;   

 }

Este mapeamento gera a seguinte saída SQL:

 create table gdf.tb_VeiculoPosicaoPneu
 (
    CodVeiculoPneus int not null
  , CodPneu         bigint not null
  , CodPosicao      varchar(255) not null
  , primary key (CodVeiculoPneus, CodPosicao)
        )
  
 alter table gdf.tb_VeiculoPosicaoPneu 
       add constraint UK_PNEU_VEICULOPOSICAOPNEU 
 unique (CodPneu)
 
 alter table gdf.tb_VeiculoPosicaoPneu 
       add constraint FK_PNEU_VEICULOSPOSICAOPNEU 
       foreign key (CodPneu )
       references gdf.tb_Pneu
    
 alter table gdf.tb_VeiculoPosicaoPneu 
       add constraint FK_VEICULOPNEUS_VEICULOPOSICAOPNEU 
       foreign key (CodVeiculoPneus) 
       references gdf.tb_VeiculoPneus

segunda-feira, 26 de agosto de 2019

Script Batch (cmd) Windows SQLServer Backup

Bom dia!

Durante o final de semana trabalhei em um script para relização do backup via prompt de comando, mas não é apenas um backup que ele realiza dentre as tarefaz atribuidas o script segue os seguintes passos:


  1. Faz backup de múltiplas bases
  2. Compacta utilizando 7-zip
  3. Move o arquivo Compactado para um diretorio na rede.
  4. Caso ocorra algum erro envia e-mail para notificar
  5. Caso ocorra tudo bem tambem envia um e-mail notificando
Requisitos para que esse script funcione corretamente:
  • SQLCMD 
  • 7z
  • Blat