Consulta extremamente lenta

Bom dia,

Estou com um problema, preciso consultar uma relação de 50.000 itens e minha consulta está muito lenta, gostaria de otimiza-la. Qual seria a melhor forma de refazer essa consulta?

public String produtoCodigo() {
        Query qry = tot.createNamedQuery("ProdutoProtheus.findByCod");
        qry.setParameter("codigo", this.getCodigo());
        setProdutosProtheus((List<ProdutoProtheus>) qry.getResultList());

        this.produtosProtheus.forEach((p) -> {
            p.setEstoqueSP(tot.find(ProdutoEstoque.class, Integer.parseInt(p.getCodigo().trim())));
            p.setEstoqueSUM(tot.find(ProdutoEstoqueSUM.class, Integer.parseInt(p.getCodigo().trim())));
        });
        
        if (produtosProtheus.isEmpty()) {
            FacesMessage fm = new FacesMessage("Nenhum registro encontrado");
            FacesContext.getCurrentInstance().addMessage("consultar", fm);
            setProdutoProtheus(null);
            return "produtos_protheus";
        }
        return "produtos_protheus";
    }

CONSIDERAÇÕES QUANTO AO LAÇO DE REPETIÇÃO FOR:

Não sou a pessoa mais indicada, entretanto, tenho em mente que em se tratando de desempenho, o for comum é muito poderoso.
Digo isto pois dos testes que fiz, o for comum levou a melhor em todos.
Uma vez fiz uma brincadeira de subtração de imagem usando for comum e para ver se dava pra otimizar, migrei a codificação para stream, ficou 20 mais lento.
Não é a última palavra, pois como disse não me considero a pessoa mais indicada.
Mas se tiver curiosidade você pode executar a seguinte codificação e ver por si mesmo.

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Ordenance {

    public static void main(String[] args) {
        ArrayList<String> alfabeto = new ArrayList<>(Arrays.asList(" ", " ", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "r", "m", "n", "o", "p", "q", "r", "s", "t", "u", "x", "z"));

        List<Produto> lista = new ArrayList<>();
        //50000 esta levando menos de 1 segundo
        for (int i = 0; i < 1000000; i++) {
            Collections.shuffle(alfabeto);
            lista.add(new Produto(alfabeto.subList(5, (int) (5 + Math.random() * 10)).toString(), alfabeto.subList(0, 10).toString(), i * 3 * Math.random()));
        }

        long tempo1, tempo2, tempo3, soma, tempoDeExecucao = System.currentTimeMillis();
        for (int l = 0; l < 30; l++) {
            System.out.println("\nCiclo: <===><><> " + (1 + l) + " <><><===>\n");
            tempo1 = -System.nanoTime();
            //for com trim()
            lista.stream().map(item -> item.getNome().trim()).collect(Collectors.toList());
            tempo1 += System.nanoTime();

            tempo2 = -System.nanoTime();
            //for sem trim()
            lista.stream().map(Produto::getNome).collect(Collectors.toList());
            tempo2 += System.nanoTime();
            System.out.println("Tempo1: " + tempo1 + " - Tempo: " + tempo2+"\nTempo 1 - tempo 2: " + (tempo1 - tempo2)
                    +"\n"+String.format("Diferença: [%.3f", 100 * (float) tempo1 / tempo2) + "%]\n=->         v   v          <-=");
            
            soma = 0;
            tempo3 = -System.nanoTime();
            //for comum
            for (int j = 0; j < lista.size(); j++) {
                soma += lista.get(j).getValor();
            }
            tempo3 += System.nanoTime();
            System.out.println("|- Tempo: " + tempo3+"\n|- Tempo 1 - tempo 3: " + (tempo1 - tempo3)
                    +String.format("\n|- Dif for comum: [%.3f", 100 * (float) tempo1 / tempo3) 
                    + "%]\nO for comum foi + rápido ? "+(tempo3< tempo1 && tempo3 < tempo2)
                    +"\n==============================");
       }
        System.out.println("Fator 3 milhões: "+Duration.of(System.currentTimeMillis() -tempoDeExecucao, ChronoUnit.MILLIS).getSeconds()+" segundos");
    }
}



public class Produto {
    
    private String nome;
    private String id;
    private double valor;

    public Produto(String nome, String id, double valor) {
        this.nome = nome;
        this.id = id;
        this.valor = valor;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public double getValor() {
        return valor;
    }

    public void setValor(double valor) {
        this.valor = valor;
    }
}

SUGESTÃO DE IMPLEMENTAÇÃO:

O problema não é consultar 50000, é o que se faz com esta consulta.
1 -p.setEstoqueSP(tot.find(ProdutoEstoque.class, Integer.parseInt(p.getCodigo().trim())));
a) o trim() consome muito recurso, pois ele vair percorrer uma string e remover o último caractere se for " ", o problema é fazer isto 50000 * o código que deve ser um número.
b) o Integer.ParseInt() vai tomar a parte dele no processamento.
c) não me ative as demais instruções nesta linha.
2 - p.setEstoqueSUM(tot.find(ProdutoEstoqueSUM.class, Integer.parseInt(p.getCodigo().trim())));
a) aparentemente faz outra consulta no banco de dados;
b) tome + trim() + Integer.ParseInt.
Sugestão:
Para corrigir item 1:
I) a interface de cadastramento não deve permitir a inserção de codificação com espaço no final, isto descartaria o uso de trim();
II) o banco de dados deve trabalhar com id numérico, portanto, não seria necessário usar o Integer.ParseInt.
III) ao invés da variável do tipo ResultSet pegar uma String no id, deve pegar um int como em seuResultSet.getInt(nomeOuNumeroDaColuna), pois assim, você teria o valor desejado com menos processamento.
Para corrigir o item 2:
I) O banco de dados pode realizar a soma, media, outras contagens e formas de consulta que você necessitar, a exemplo de:
II) para contar os produtos, você precisaria de uma consulta simples, a exemplo de:



Obs.: se o cliente não vai visualizar 50000 itens, passe lotes de 50, 100, ou 200, e a medida em que o cliente vai visualizando, você vai realizando pequenas consultas no banco de dados e “populando” o view desejado.
Em se tratando de relatórios, faça uso do banco de dados e só passe pra frente o que for necessário.

Então, a melhor avaliação é o banco de dados pode estar sendo sub utilizado e está sobrecarregando a aplicação do cliente.
Mas, há outros participantes no fórum com melhor visão sobre o tema e eu não tenho um domínio satisfatório de ambas as tecnologias (banco de dados e aplicação cliente).
Té+.

50.000 de objetos em memória? :scream:

Já estudou deixar essa consulta para o banco retornar (tabela com índices, procedures, views etc…)?

Você obrigatoriamente precisa retornar tudo em objetos, estudou a possibilidade de retornar um arquivo pronto da base?

São apenas sugestões ok? Mas eu buscaria melhorar o banco primeiro, para depois ver o código… e na minha opinião linguagens oo possuem um overhead na memória brutal se comparadas a linguagens somente procedurais por exemplo…

1 curtida

Não acho que o paradigma de orientação a objeto seja um problema, vejo muita beleza nesta forma de abstração.
Ex.: eu não uso hibernate, faço a comunicação diretamente com o banco de dados com uma classe que se não me engano (não sou desenvolvedor) é classificada como DAO, Data Acess Object.
O mais complicado que fiz até agora foi mandar arquivos de mídia para o banco de dados, sem usar CLOB ou BLOB e recuperar a informação, usando somente o “conceito” de stream e classificação.
No exemplo de codificação, acima, coloquei 1 milhão de objetos na memória para testar o desempenho do for pois 50000 não tinha nem graça visto que estava “zerando” tempo.


Não vejo a orientação a objeto como rival da procedual ou imperativa, tenho as tecnologias como ferramentas de finalidade, pois há coisas onde se usa preferencialmente java, outras c, outras python, outras R e lá se vai.
:rocket:
Não é uma crítica, apenas um compartilhamento de opinião, podendo naturalmente estar errado também.

Com certeza, um debate saudável só agrega ao fórum…

Com relação ao paradigma, hoje em dia com a otimização da jvm, isso não se percebe tanto, mas dependendo do uso, causa um overhead, pois a cada new gerado (instância de objeto) além de ter essa referência em memória, ainda há um conjunto de atributos (cada um reserva seu espaço na memória, etc) e dependendo da implementação da classe, se ela possui atributos como listas por exemplo, ou se houver herança (extends) o consumo vai aumentando (principalmente aplicativos desktop usando swing já que desde os botões até as telas são objetos)… antigamente se quisesse causar um OutOfMemoryError era só tentar…

public class Teste {
    private List<String> lista = new ArrayList<>();

    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            new Teste();
        }
    }
}

Executei esse código e a princípio não tomei Exception (minha máquina tem 16GB de memória), mas é uma boa brincadeira pra ver até onde a jvm aguenta o tranco :joy:

Quanto a usar streams, o conceito é um pouco diferente, pois trabalha a nível de bytes (ainda não estudei a fundo no java 8, mas em php, python e até C por exemplo é assim creio que java não seja diferente) e como byte em java é tipo primitivo, acaba sendo otimizado por natureza… mas esse é meu ponto de vista, obviamente os mais entendidos podem opinar a respeito…

Agora como sou “das antigas”, sempre tive a premissa de verificar primeiro as rotinas do banco de dados, ver se a query está pesando diretamente no banco e ir “subindo” até o nível da aplicação…

1 curtida

Acho que foi o melhor que tive até agora!!!
Excelente link sobre o estouro de memória.
Vlws.
:wink:

1 curtida

Para processamento em lote não jogue tudo pra memória, ou por que precisa dos 50 mil itens simultaneamente na memória? Descarte JPA/Hibernate, pesado por natureza e nesse caso vira uma bomba. Se for obrigado a usar Hibernate, pelo menos use StatelessSession .

Para Java, faz o feijão com arroz: JDBC + SQL diretamente sob seu controle, percorrendo o resultset sem criar objetos. Se não precisa dos 50 mil itens simultaneamente em memória, concordo com o colega acima que é overhead ficar criando objetos.

2 curtidas