Guia Hibernate

Controlando múltiplas coleções com Hibernate

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (5)  (0)

Veja neste artigo como trabalhar com múltiplas coleções de objetos no Hibernate, contornando o erro "Multiple Bags", muito comum de ocorrer quando se tem mais de um mapeamento do tipo EAGER.


Figura 1: Logo do Hibernate

Introdução

O Hibernate é um framework para mapeamento objeto-relacional ou como também é conhecido originalmente do inglês, ORM (Object-Relational Mapping). O Hibernate tem como objetivo abstrair a complexidade, no tocante a disparidade do paradigma Orientado a Objetos x Modelo de Dados Relacional. Porém, há uma série de configurações a serem realizadas, para que a aplicação tenha o resultado esperado para o projeto. Dentre tantas configurações é comum deparar-se com erros/exceções. Logo, esse artigo relata uma falha comum ao mapear/configurar coleções de dados em entidades da aplicação.

O Problema

Quando trabalhamos com mais de uma coleção de dados, mapeada com o Hibernate, é natural obtermos um erro conhecido como: Multiple Bags. Isso ocorre porque uma associação/coleção pode ser tachada como uma Bag pelo Hibernate, por termos mais de um mapeamentos do tipo EAGER (como estratégia de busca), na mesma entidade, ou no grafo de objetos.

Listagem 1: Erro encontrado

Caused by: org.hibernate.HibernateException:
cannot simultaneously fetch multiple bags

Em Java, não existe implementação para o tipo Bag, somente: List, Map e Set.

O Bag representa uma coleção desordenada de objetos e permite a duplicidade.

Logo, cabe a pergunta: “Por que Java não tem uma implementação que represente uma estrutura Bag, mas o Hibernate identifica um List como sendo um Bag?”. Para respondermos essa pergunta, vamos primeiro entender como funciona internamente a estrutura de dados conhecida como List.

List – é uma coleção de dados ordenada, porém permite a duplicidade de objetos. Em Java, temos as seguintes implementações de List: ArrayList, Vector e a LinkedList.

Agora, imagine o seguinte cenário: poderemos ter duas coleções EAGER, ou seja, configurada de forma a trazer todos os itens do banco de uma só vez (estratégia que normalmente chamamos de busca ansiosa), através do seguinte código:

Listagem 2: Mapeamento de uma List de forma errada

@Entity(name = "tbcliente")
public class Cliente implements Serializable{
	@Id
	@Column(name = "id", nullable = false)
	private Integer codigo;

	@CollectionOfElements(fetch = FetchType.EAGER)
	private List<String> emails;

	@CollectionOfElements(fetch = FetchType.EAGER)
	private  List<String> telefones;

Ao tentar executar uma operação com um objeto da classe/entidade Cliente, receberemos o mesmo erro já exposto no início do post [simultaneously fetch multiple bags]. Esse erro ocorre devido o Hibernate ter como comportamento padrão, reconhecer a List, quando mapeada dessa forma, como um Bag. Pois em cenários como esse, apenas uma das coleções podem ser configuradas como EAGER, as outras deveriam ser mapeadas como LAZY. Podemos considerar a configuração correta do mapeamento, como uma primeira opção para o Hibernate não detectar um Bag.

A segunda alternativa seria indicar ao Hibernate que essa coleção é uma lista de dados ordenada. Para tal, precisamos utilizar a anotação: @IndexColumn. Veja exemplo abaixo:

Listagem 3: Adicionando índice

@Entity(name = "tbcliente")
public class Cliente implements Serializable{
	@Id
	@Column(name = "id", nullable = false)
	private Integer codigo;

	@CollectionOfElements(fetch = FetchType.EAGER)
	@IndexColumn(name = "email")
	private List<String> emails;

	@CollectionOfElements(fetch = FetchType.EAGER)
	@IndexColumn(name = "fones")
	private  List<String> telefones;

Vejamos o que aconteceu agora:

No código acima da Listagem 3, foram mapeadas duas Coleções do tipo List, onde a partir do momento em que as configuramos com a anotação @IndexColumn, o Hibernate passa a reconhecê-las como um lista de objetos ordenados. No banco de dados será criada uma tabela, onde a mesma terá uma coluna que mantém o índice, o qual a função desse, é manter a ordem na qual fora adicionado o e-mail ou telefone. Veja uma representação da tabela criada pelo Hibernate abaixo:

tbcliente_id [pk]ElementEmail [pk]
1carlos@gmail.com0
1maria@gmail.com1
1andrea@gmail.com2

Tabela 1: Tabela criada pelo Hibernate para manter dados da coleção List

A Tabela 1, indica que para o objeto cliente de ID/Codigo = 1, temos três e-mails cadastrados onde, a coluna Email, mantém a ordem de inserção, ou seja, o e-mail [carlos@gmail.com] foi o primeiro a ser adicionado na lista. Dessa forma, quando recuperarmos o cliente dono desses e-mails, teremos uma lista com e-mails na mesma ordem que foram salvos.

Uma outra opção, ainda para resolver o mesmo problema de múltiplas coleções em um grafo de objeto, é: ao invés de utilizar java.util.List para manter os objetos, opte por usar uma implementação de Set ou ainda um SortedSet. Veja abaixo como ficaria com uma coleção do tipo Set:

Listagem 4: Mapeamento de coleções com Set

@Entity(name = "tbcliente")
public class Cliente implements Serializable{
	@Id
	@Column(name = "id", nullable = false)
	private Integer codigo;

	@CollectionOfElements(fetch = FetchType.EAGER)
	Set<String> emails;

	@CollectionOfElements(fetch = FetchType.EAGER)
	Set<String> telefones;

Com o código acima, conseguimos garantir a unicidade dos objetos, pois como já sabemos as coleções do tipo Set, não permitem duplicatas de objetos. Você ainda poderá fazer como no código abaixo, que usa um SortedSet, que nesse caso, precisa-se indicar como será ordenador por: ordem NATURAL ou COMPARATOR. Para isso utilize a anotação @Sort.

Listagem 5: Utilizando SortedSet

@Entity(name = "tbcliente")
public class Cliente implements Serializable{
	@Id
	@Column(name = "id", nullable = false)
	private Integer codigo;

	@CollectionOfElements(fetch = FetchType.EAGER)
	@IndexColumn(name = "email")
	@Sort(type = SortType.NATURAL)
	SortedSet<String> emails;

	@CollectionOfElements(fetch = FetchType.EAGER)
	@Sort(ype = Sorte.NATURAL)
	SortedSet<String> telefones;

Conclusão

O problema do qual é tratado nesse artigo, é comum no dia a dia dos desenvolvedores Java que trabalham com o framework Hibernate. Contudo, nos cabe avaliar, qual a melhor estratégia a ser utilizada no projeto. Pois, assim como várias implementações a serem feitas em projetos de software, tudo depende do cenário que será aplicado e dos requisitos do sistema.

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?