Digamos que eu tenha uma tabela com milhões de linhas. Usando JPA, qual é a maneira correta de iterar em uma consulta nessa tabela, de forma que eu não tenha uma lista inteira na memória com milhões de objetos?
Por exemplo, suspeito que o seguinte explodirá se a mesa for grande:
List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();
for (Model model : models)
{
System.out.println(model.getId());
}
A paginação (em loop e atualização manual setFirstResult()
/ setMaxResult()
) é realmente a melhor solução?
Editar : o principal caso de uso que estou visando é uma espécie de trabalho em lote. Tudo bem se demorar muito para ser executado. Não há cliente da web envolvido; Eu só preciso "fazer algo" para cada linha, uma (ou um pequeno N) de cada vez. Estou apenas tentando evitar tê-los todos na memória ao mesmo tempo.
Respostas:
A página 537 do Java Persistence with Hibernate dá uma solução usando
ScrollableResults
, mas infelizmente é apenas para Hibernate.Portanto, parece que usar
setFirstResult
/setMaxResults
e iteração manual é realmente necessário. Esta é minha solução usando JPA:então, use-o assim:
fonte
size() == 100
irá pular uma consulta adicional que retorna uma lista vaziaTentei as respostas apresentadas aqui, mas JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2 não funcionou com eles. Acabamos de migrar do JBoss 4.x para o JBoss 5.1, portanto, continuamos com ele por enquanto e, portanto, o Hibernate mais recente que podemos usar é o 3.3.2.
Adicionar alguns parâmetros extras funcionou, e um código como este é executado sem OOMEs:
As linhas cruciais são os parâmetros de consulta entre createQuery e scroll. Sem eles, a chamada "scroll" tenta carregar tudo na memória e nunca termina ou executa para OutOfMemoryError.
fonte
Você realmente não pode fazer isso em JPA direto, entretanto o Hibernate tem suporte para sessões sem estado e conjuntos de resultados roláveis.
Rotineiramente processamos bilhões de linhas com sua ajuda.
Aqui está um link para a documentação: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
fonte
Para ser honesto, eu sugeriria deixar o JPA e ficar com o JDBC (mas certamente usando a
JdbcTemplate
classe de suporte ou algo semelhante). O JPA (e outros provedores / especificações ORM) não foi projetado para operar em muitos objetos dentro de uma transação, pois eles assumem que tudo carregado deve permanecer no cache de primeiro nível (daí a necessidade declear()
no JPA).Também estou recomendando uma solução de nível mais baixo porque a sobrecarga do ORM (a reflexão é apenas a ponta de um iceberg) pode ser tão significativa, que iterar sobre o plano
ResultSet
, mesmo usando algum suporte leve como mencionadoJdbcTemplate
, será muito mais rápido.O JPA simplesmente não foi projetado para executar operações em uma grande quantidade de entidades. Você pode brincar com
flush()
/clear()
para evitarOutOfMemoryError
, mas considere isso mais uma vez. Você ganha muito pouco pagando o preço do enorme consumo de recursos.fonte
flush()
/clear()
. O primeiro é IMHO não projetado para fins de processamento em lote, enquanto usando a sequência de flush () / clear () cheira a abstração com vazamento .Se você usar EclipseLink I 'usando este método para obter o resultado como Iterable
fechar Método
fonte
Depende do tipo de operação que você precisa fazer. Por que você está repetindo um milhão de linhas? Você está atualizando algo no modo em lote? Você vai exibir todos os registros para um cliente? Você está computando algumas estatísticas sobre as entidades recuperadas?
Se você vai exibir um milhão de registros para o cliente, reconsidere sua interface de usuário. Nesse caso, a solução apropriada é paginar seus resultados e usar
setFirstResult()
esetMaxResult()
.Se você lançou uma atualização de uma grande quantidade de registros, é melhor manter a atualização simples e de uso
Query.executeUpdate()
. Opcionalmente, você pode executar a atualização no modo assíncrono usando um Message-Driven Bean oa Work Manager.Se estiver computando algumas estatísticas sobre as entidades recuperadas, você pode tirar vantagem das funções de agrupamento definidas pela especificação JPA.
Para qualquer outro caso, seja mais específico :)
fonte
SELECT m.id FROM Model m
e então iterar em um List <Integer>.Não há "apropriado" o que fazer isso, isso não é o que JPA ou JDO ou qualquer outro ORM se destina a fazer, JDBC direto será sua melhor alternativa, pois você pode configurá-lo para trazer de volta um pequeno número de linhas em uma vez e esvazie-os à medida que são usados, é por isso que existem cursores do lado do servidor.
As ferramentas ORM não são projetadas para processamento em massa, elas são projetadas para permitir que você manipule objetos e tente fazer com que o RDBMS em que os dados são armazenados seja o mais transparente possível, a maioria falha na parte transparente pelo menos em algum grau. Nessa escala, não há como processar centenas de milhares de linhas (Objetos), muito menos milhões com qualquer ORM e executá-lo em qualquer período de tempo razoável por causa do overhead de instanciação do objeto, puro e simples.
Use a ferramenta apropriada. JDBC direto e procedimentos armazenados definitivamente têm um lugar em 2011, especialmente no que eles fazem melhor em comparação com essas estruturas ORM.
Puxar um milhão de qualquer coisa, mesmo de uma forma simples,
List<Integer>
não será muito eficiente, independentemente de como você o faz. A maneira correta de fazer o que você está pedindo é simplesSELECT id FROM table
, defina comoSERVER SIDE
(dependente do fornecedor) e coloque o cursor emFORWARD_ONLY READ-ONLY
e itere sobre isso.Se você realmente está puxando milhões de ids para processar chamando algum servidor da web com cada um, você terá que fazer algum processamento simultâneo para que isso seja executado em um período de tempo razoável. Puxar com um cursor JDBC e colocar alguns deles por vez em um ConcurrentLinkedQueue e ter um pequeno pool de threads (# CPU / Cores + 1) puxar e processá-los é a única maneira de completar sua tarefa em uma máquina com qualquer " "normal" de RAM, visto que você já está ficando sem memória.
Veja esta resposta também.
fonte
Você pode usar outro "truque". Carregue apenas a coleção de identificadores das entidades nas quais você está interessado. Digamos que o identificador seja do tipo long = 8 bytes, então 10 ^ 6 uma lista de tais identificadores totaliza cerca de 8 MB. Se for um processo em lote (uma instância por vez), é suportável. Em seguida, apenas itere e faça o trabalho.
Outra observação - você deve fazer isso em partes - especialmente se modificar os registros, caso contrário, o segmento de rollback no banco de dados aumentará.
Quando se trata de definir a estratégia firstResult / maxRows - será MUITO, MUITO lento para resultados distantes do topo.
Também leve em consideração que o banco de dados provavelmente está operando em isolamento de leitura confirmada , para evitar leituras fantasmas, carregue os identificadores e carregue as entidades uma a uma (ou 10 por 10 ou o que for).
fonte
Fiquei surpreso ao ver que o uso de procedimentos armazenados não foi mais proeminente nas respostas aqui. No passado, quando eu tinha que fazer algo assim, eu crio um procedimento armazenado que processa dados em pequenos pedaços, depois dorme um pouco e depois continua. O motivo para dormir é não sobrecarregar o banco de dados, que provavelmente também está sendo usado para tipos de consultas em tempo real, como a conexão a um site. Se não houver mais ninguém usando o banco de dados, você pode deixar de dormir. Se você precisar garantir que processa cada registro uma vez e apenas uma vez, precisará criar uma tabela (ou campo) adicional para armazenar quais registros você processou a fim de ser resiliente nas reinicializações.
As economias de desempenho aqui são significativas, possivelmente ordens de magnitude mais rápidas do que qualquer coisa que você pudesse fazer em JPA / Hibernate / AppServer, e seu servidor de banco de dados provavelmente terá seu próprio tipo de mecanismo de cursor do lado do servidor para processar grandes conjuntos de resultados com eficiência. A economia de desempenho vem de não ter que enviar os dados do servidor de banco de dados para o servidor de aplicativos, onde você processa os dados e depois os envia de volta.
Existem algumas desvantagens significativas em usar procedimentos armazenados que podem descartar completamente isso para você, mas se você tiver essa habilidade em sua caixa de ferramentas pessoal e puder usá-la neste tipo de situação, você pode eliminar esses tipos de coisas rapidamente .
fonte
Para expandir a resposta de @Tomasz Nurkiewicz. Você tem acesso ao
DataSource
que, por sua vez, pode fornecer uma conexãoEm seu código você tem
Isso permitirá que você ignore o JPA para algumas operações de lote grandes específicas, como importação / exportação, no entanto, você ainda terá acesso ao gerenciador de entidade para outras operações JPA, se precisar.
fonte
Use o
Pagination
conceito para recuperar o resultadofonte
Eu mesmo me perguntei isso. Parece importar:
Eu escrevi um Iterator para facilitar a troca de ambas as abordagens (findAll vs findEntries).
Eu recomendo que você tente ambos.
Acabei não usando meu iterador de chunk (então pode não ter sido testado). A propósito, você precisará de coleções do Google se quiser usá-lo.
fonte
Com a hibernação, existem 4 maneiras diferentes de conseguir o que você deseja. Cada um tem compensações, limitações e consequências de design. Sugiro explorar cada um e decidir qual é o certo para a sua situação.
fonte
Aqui está um exemplo JPA simples e direto (em Kotlin) que mostra como você pode paginar sobre um conjunto de resultados arbitrariamente grande, lendo pedaços de 100 itens por vez, sem usar um cursor (cada cursor consome recursos no banco de dados). Ele usa paginação de conjunto de chaves.
Consulte https://use-the-index-luke.com/no-offset para o conceito de paginação de conjunto de chaves e https://www.citusdata.com/blog/2016/03/30/five-ways-to- paginar / para uma comparação de diferentes maneiras de paginar junto com suas desvantagens.
fonte
Um exemplo com JPA e NativeQuery buscando toda vez que o tamanho Elements usando offsets
fonte