O Hibernate lança MultipleBagFetchException - não pode buscar simultaneamente vários pacotes

471

O Hibernate lança essa exceção durante a criação do SessionFactory:

org.hibernate.loader.MultipleBagFetchException: não é possível buscar simultaneamente vários pacotes

Este é o meu caso de teste:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 // @IndexColumn(name="INDEX_COL") if I had this the problem solve but I retrieve more children than I have, one child is null.
 private List<Child> children;

}

Child.java

@Entity
public Child {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private Parent parent;

}

E esse problema? O que eu posso fazer?


EDITAR

OK, o problema que tenho é que outra entidade "pai" está dentro do meu pai, meu comportamento real é este:

Parent.java

@Entity
public Parent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @ManyToOne
 private AnotherParent anotherParent;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<Child> children;

}

AnotherParent.java

@Entity
public AnotherParent {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;

 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 private List<AnotherChild> anotherChildren;

}

O Hibernate não gosta de duas coleções FetchType.EAGER, mas isso parece ser um bug, não estou fazendo coisas incomuns ...

Remoção FetchType.EAGERde Parentou AnotherParentresolve o problema, mas eu preciso dele, então verdadeira solução é usar @LazyCollection(LazyCollectionOption.FALSE)em vez de FetchType(graças a Bozho para a solução).

golpe
fonte
Gostaria de perguntar, qual consulta SQL você espera gerar que recuperará duas coleções separadas simultaneamente? Os tipos de SQL que seriam capazes de alcançá-los exigiriam uma junção cartesiana (potencialmente altamente ineficiente) ou uma UNIÃO de colunas disjuntas (também feias). Presumivelmente, a incapacidade de conseguir isso no SQL de maneira limpa e eficiente influenciou o design da API.
Thomas W
@ThomasW Estas são as consultas sql que ele deve gerar:select * from master; select * from child1 where master_id = :master_id; select * from child2 where master_id = :master_id
nurettin
1
Você pode obter um erro semelhante se tiver mais de um List<child>com fetchTypedefinido para mais de um List<clield>
Big Zed

Respostas:

555

Eu acho que uma versão mais recente do hibernate (com suporte ao JPA 2.0) deve lidar com isso. Caso contrário, você pode contornar os campos da coleção com:

@LazyCollection(LazyCollectionOption.FALSE)

Lembre-se de remover o fetchTypeatributo da @*ToManyanotação.

Mas observe que, na maioria dos casos, a Set<Child>é mais apropriado do que List<Child>, portanto, a menos que você realmente precise de List- vá paraSet

Mas lembre-se de que, com o uso de conjuntos, você não eliminará o Produto Cartesiano subjacente, conforme descrito por Vlad Mihalcea em sua resposta !

Bozho
fonte
4
estranho, funcionou para mim. Você removeu o fetchTypedo @*ToMany?
Bozho
101
o problema é que as anotações da JPA são analisadas para não permitir mais do que duas coleções carregadas avidamente. Mas as anotações específicas do hibernate permitem.
Bozho
14
A necessidade de mais de 1 EAGER parece totalmente realista. Essa limitação é apenas uma supervisão da JPA? Quais são as preocupações que devo procurar ao ter vários EAGERs?
AR3Y35
6
o problema é que o hibernate não pode buscar as duas coleções com uma consulta. Portanto, quando você consulta a entidade pai, ela precisa de 2 consultas extras por resultado, o que normalmente é algo que você não deseja.
Bozho
7
Seria ótimo ter uma explicação de por que isso resolve o problema.
Webnet
290

Simplesmente mude de um Listtipo para outro Set.

Mas lembre-se de que você não eliminará o Produto Cartesiano subjacente, conforme descrito por Vlad Mihalcea em sua resposta !

Ahmad Zyoud
fonte
42
Uma lista e um conjunto não são a mesma coisa: um conjunto não preservar a ordem
Matteo
17
LinkedHashSet preserva a ordem
egallardo 16/05
15
Essa é uma distinção importante e, quando você pensa sobre isso, está totalmente correta. O típico muitos-para-um implementado por uma chave estrangeira no banco de dados não é realmente uma lista, é um conjunto porque a ordem não é preservada. Então Set é realmente mais apropriado. Eu acho que isso faz a diferença no hibernar, embora eu não saiba o porquê.
Fool4jesus 6/12/12
3
Eu estava tendo o mesmo, não posso buscar simultaneamente várias malas, mas não por causa das anotações. No meu caso, eu estava fazendo junções esquerdas e disjunções com as duas *ToMany. Alterar o tipo para Setresolver o meu problema também. Solução excelente e arrumada. Essa deve ser a resposta oficial.
L. Holanda
20
Gostei da resposta, mas a pergunta de um milhão de dólares é: Por quê? Por que com Set não mostra exceções? Obrigado
Hinotori
140

Adicione uma anotação @Fetch específica do Hibernate ao seu código:

@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Fetch(value = FetchMode.SUBSELECT)
private List<Child> childs;

Isso deve corrigir o problema relacionado ao bug do Hibernate HHH-1718

Dave Richardson
fonte
5
@DaveRlz por que o subSelect resolve esse problema. Eu tentei sua solução e está funcionando, mas não sei como o problema foi resolvido usando isso?
precisa saber é o seguinte
Esta é a melhor resposta, a menos que Setrealmente faça sentido. Ter um único OneToManyrelacionamento usando Setresultados em 1+<# relationships>consultas, enquanto que usando FetchMode.SUBSELECTresultados em 1+1consultas. Além disso, o uso da anotação na resposta aceita ( LazyCollectionOption.FALSE) faz com que ainda mais consultas sejam executadas.
Mstrthealias
1
FetchType.EAGER não é uma solução adequada para isso. Necessidade de prosseguir com Hibernate Fetch Perfis e necessidade de resolvê-lo
Milinda Bandara
2
As duas outras respostas principais não resolveram o meu problema. Este fez. Obrigado!
Blindworks
3
Alguém sabe por que SUBSELECT corrige, mas JOIN não?
Innokenty
42

Essa pergunta tem sido um tema recorrente no StackOverflow ou no fórum Hibernate, então decidi transformar a resposta em um artigo também.

Considerando que temos as seguintes entidades:

insira a descrição da imagem aqui

E você deseja buscar algumas Postentidades pai junto com todas as coleções commentse tags.

Se você estiver usando mais de uma JOIN FETCHdiretiva:

List<Post> posts = entityManager
.createQuery(
    "select p " +
    "from Post p " +
    "left join fetch p.comments " +
    "left join fetch p.tags " +
    "where p.id between :minId and :maxId", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.getResultList();

O Hibernate lançará o infame:

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags [
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.comments,
  com.vladmihalcea.book.hpjp.hibernate.fetching.Post.tags
]

O Hibernate não permite buscar mais de uma bolsa, pois isso geraria um produto cartesiano .

A pior "solução"

Agora, você encontrará muitas respostas, postagens de blog, vídeos ou outros recursos solicitando que você use um em Setvez de um Listpara suas coleções.

Esse é um conselho terrível. Não faça isso!

Usar em Setsvez de Listsfará MultipleBagFetchExceptiondesaparecer, mas o Produto Cartesiano ainda estará lá, o que é ainda pior, pois você descobrirá o problema de desempenho muito depois de aplicar essa "correção".

A solução adequada

Você pode executar o seguinte truque:

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id between :minId and :maxId ", Post.class)
.setParameter("minId", 1L)
.setParameter("maxId", 50L)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.tags t " +
    "where p in :posts ", Post.class)
.setParameter("posts", posts)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();

Na primeira consulta JPQL, distinctNÃO vai para a instrução SQL. É por isso que configuramos a PASS_DISTINCT_THROUGHdica de consulta JPA para false.

DISTINCT tem dois significados no JPQL e, aqui, precisamos deduplicar as referências de objetos Java retornadas pelo getResultListlado Java, não pelo lado SQL. Confira este artigo para mais detalhes.

Contanto que você busque no máximo uma coleção usando JOIN FETCH, você ficará bem.

Ao usar várias consultas, você evitará o produto cartesiano, pois qualquer outra coleção, mas a primeira é buscada usando uma consulta secundária.

Há mais que você poderia fazer

Se você estiver usando a FetchType.EAGERestratégia no momento do mapeamento para @OneToManyou @ManyToManyassociações, poderá facilmente terminar com a MultipleBagFetchException.

É melhor mudar de FetchType.EAGERpara, Fetchype.LAZYpois a busca ansiosa é uma péssima idéia que pode levar a problemas críticos de desempenho do aplicativo .

Conclusão

Evite FetchType.EAGERe não mude de Listpara Setapenas porque isso fará com que o Hibernate oculte o MultipleBagFetchExceptionsob o tapete. Busque apenas uma coleção de cada vez, e você ficará bem.

Contanto que você faça isso com o mesmo número de consultas que possui coleções para inicializar, você estará bem. Apenas não inicialize as coleções em um loop, pois isso desencadeará problemas de consulta N + 1 , que também prejudicam o desempenho.

Vlad Mihalcea
fonte
Obrigado pelo conhecimento compartilhado. No entanto, DISTINCTé assassino de desempenho nesta solução. Existe uma maneira de se livrar distinct? (tentou retornar Set<...>em vez disso, não ajuda muito)
Leonid Dashko
1
DISTINCT não vai para a instrução SQL. É por isso que PASS_DISTINCT_THROUGHestá definido como false. DISTINCT tem 2 significados no JPQL, e aqui, precisamos deduplicar no lado Java, não no SQL. Confira este artigo para mais detalhes.
18719 Vlad Vladinhalea
Vlad, obrigado pela ajuda, acho realmente útil. No entanto, o problema estava relacionado a hibernate.jdbc.fetch_size(eventualmente eu o configurei para 350). Por acaso, você sabe como otimizar relações aninhadas? Por exemplo entity1 -> entity2 -> entity3.1, entidade 3.2 (onde entity3.1 / 3.2 são relações @OneToMany)
Leonid Dashko
1
@LeonidDashko Confira o capítulo Fetching no meu livro Java Persistence de alto desempenho para obter muitas dicas relacionadas à busca de dados.
precisa saber é o seguinte
1
Não, você não pode. Pense nisso em termos de SQL. Você não pode ingressar em várias associações um-para-muitos sem gerar um produto cartesiano.
Vlad Mihalcea
31

Depois de tentar com todas as opções descritas nessas postagens e em outras, cheguei à conclusão de que a correção é a seguinte.

Em todo lugar XToMany @ XXXToMany(mappedBy="parent", fetch=FetchType.EAGER) e intermediariamente após

@Fetch(value = FetchMode.SUBSELECT)

Isso funcionou para mim

Javier
fonte
5
adicionar @Fetch(value = FetchMode.SUBSELECT)foi suficiente
user2601995 25/02
1
Esta é uma solução apenas para o Hibernate. E se você estiver usando uma biblioteca JPA compartilhada?
Michel
3
Tenho certeza que você não quis dizer, mas DaveRlz já escreveu a mesma coisa 3 anos antes
phil294
21

Para corrigi-lo, basta Setsubstituir Listo objeto aninhado.

@OneToMany
Set<Your_object> objectList;

e não se esqueça de usar fetch=FetchType.EAGER

vai funcionar.

Há mais um conceito CollectionIdno Hibernate, se você deseja manter apenas a lista.

Mas lembre-se de que você não eliminará o Produto Cartesiano subjacente, conforme descrito por Vlad Mihalcea em sua resposta !

Prateek Singh
fonte
6

você pode manter as listas do EAGER do estande na JPA e adicionar a pelo menos uma delas a anotação da JPA @OrderColumn (com obviamente o nome de um campo a ser solicitado). Não há necessidade de anotações de hibernação específicas. Mas lembre-se de que ele pode criar elementos vazios na lista se o campo escolhido não tiver valores começando em 0

 [...]
 @OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
 @OrderColumn(name="orderIndex")
 private List<Child> children;
 [...]

em Filhos, você deve adicionar o campo orderIndex

Massimo
fonte
2

Tentamos definir em vez de lista e é um pesadelo: quando você adiciona dois novos objetos, equals () e hashCode () não conseguem distinguir os dois! Porque eles não têm identificação.

ferramentas típicas como o Eclipse geram esse tipo de código a partir de tabelas do banco de dados:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
}

Você também pode ler este artigo que explica corretamente como o JPA / Hibernate está bagunçado. Depois de ler isso, acho que é a última vez que uso ORM em minha vida.

Também encontrei caras do Design Orientado a Domínio que basicamente dizem que o ORM é uma coisa terrível.

Mike Dudley
fonte
1

Quando você tem objetos muito complexos com coleção saveral, não seria uma boa ideia tê-los com o EAGER fetchType, use melhor o LAZY e, quando você realmente precisar carregar as coleções, use: Hibernate.initialize(parent.child)para buscar os dados.

Felipe Cadena
fonte
0

Para mim, o problema era ter aninhados no EAGER .

Uma solução é definir os campos aninhados como LAZY e usar Hibernate.initialize () para carregar os campos aninhados:

x = session.get(ClassName.class, id);
Hibernate.initialize(x.getNestedField());
butter_prune
fonte
0

No final, isso aconteceu quando eu tive várias coleções com o FetchType.EAGER, assim:

@ManyToMany(fetch = FetchType.EAGER, targetEntity = className.class)
@JoinColumn(name = "myClass_id")
@JsonView(SerializationView.Summary.class)
private Collection<Model> ModelObjects;

Além disso, as coleções estavam se juntando na mesma coluna.

Para resolver esse problema, alterei uma das coleções para FetchType.LAZY, pois estava tudo bem no meu caso de uso.

Boa sorte! ~ J

JAD
fonte
0

Comentar Fetche LazyCollectionàs vezes ajuda a executar o projeto.

@Fetch(FetchMode.JOIN)
@LazyCollection(LazyCollectionOption.FALSE)
prisar
fonte
0

Uma coisa boa @LazyCollection(LazyCollectionOption.FALSE)é que vários campos com esta anotação podem coexistir enquanto FetchType.EAGERnão podem, mesmo nas situações em que tal coexistência é legítima.

Por exemplo, um Orderpode ter uma lista de OrderGroup(uma curta) e uma lista de Promotions(também curta). @LazyCollection(LazyCollectionOption.FALSE)pode ser usado em ambos sem causar LazyInitializationExceptionnenhum MultipleBagFetchException.

No meu caso @Fetch, resolveu meu problema de, MultipleBacFetchExceptionmas depois causa LazyInitializationException, o no Sessionerro infame .

WesternGun
fonte
-5

Você pode usar uma nova anotação para resolver isso:

@XXXToXXX(targetEntity = XXXX.class, fetch = FetchType.LAZY)

De fato, o valor padrão da busca também é FetchType.LAZY.

leee41
fonte
5
JPA3.0 não existe.
precisa saber é o seguinte