Aplicações corporativas são essencialmente definidas por sua necessidade de colher, tranformar e exibir uma quantidade enorme de informações. Java Persistence API (JPA) é o framework padrão de aplicações java para fazer a ponte entre o domínio orientado a objetos e o modelo relacional de banco de dados.
Coleções do tipo
Collection, List, Set, Map são umas das estruturas de dados mais utilizadas e o JPA permite seu mapeamento para o modelo relacional
de forma simples e eficiente. Você só precisa utilizar as anotações adequadas e
não se preocupar como as coleções estão sendo representadas no banco de dados! Vejamos os casos mais comuns de mapeamento de coleções, sejam as coleções classes de entidade, classes embutidas ou tipos básicos:
- Multiplicidade simples entre uma classe de entidade e classes não-entidade (classes embutidadas)
Exemplo: Uma empresa pode ter muitos endereços (Endereço aqui é uma classe embutida):
@Embeddable
class Endereco{}
@Entity
class Empresa{
@ElementCollection
Set<Endereco> enderecos;
//para classes embutidas, utilize @ElementCollection.
}
Opcionalmente você pode especificar como o JPA deve gerar a tabela de coleções no banco de dados fornecendo alguns parâmetros:
@Entity
class Empresa{
@Id int idEmpresa;
@ElementCollection
@CollectionTable(name="logradouro", joinColumns=@JoinColumn(name="emp_id"))
@AttributeOverride(name="address", column=@Column(name="o_nome_q_vc_quiser"))
Collection<Endereco> enderecos;
}
No exemplo anterior, para representar a coleção de endereços da empresa, dissemos para o JPA criar uma tabela chamada logradouro. @JoinColumn representa a chave estrangeira.
Usar a anotação
@ElementCollection é o mínimo que você precisa fazer para mapear a maioria dos tipos de coleções. Nos próximos exemplos vamos explorar coleções mais sofisticadas utilizando índices.
Multiplicidade indexada (pares chave/valor)
Pares chave/valor são representados pela interface Map. Coleções desse tipo são muito comuns e são utilizadas em praticamente todas as aplicações. O JPA é bastante flexível com tipos Map. As entidades ou classes embutidas podem representar tanto a chave quanto o valor, ou ainda chave e valor podem ser tipos primitivos.
1 Chave e Valor são tipos primitivos
Exemplo: Um funcionário pode ter vários números de telefone e cada número é de um tipo (comercial, trabalho, casa, etc.):
Essencialmente você só precisa da usar a anotação @ElementCollection para resolver seu problema. Todas as outras são opcionais.
@Entity
public class Funcionario{
@Id private int id;
@ElementCollection
@CollectionTable(name="funcionario_telefone") //opcional, define o nome da tabela no banco
@MapKeyColumn(name="tipo_telefone") ///opcional indica qual coluna armazena achave
@Column(name="numero") //nome da coluna que armazena o valor
private Map<String, String> telefones;
//...
}
2 A chave do Map pode ser uma enum ou uma data
Constantes enumeradas dão mais consistência e legibilidade ao código. Como no exemplo anterior, mas ao invés de usarmos um tipo básico como chave, usamos uma
enum. Novamente a única anotação obrigatória é
@ElementCollection, mas podemos definir como o valor da
enumeration será armazenado no banco de dados, se sob a forma literal ou numérica, assim precisamos utilizar a anotação @
MapKeyEnumerated:
public enum TipoTelefone {HOME, MOBILE, WORK}
@Entity
public class Funcionario{
@Id private int id;
@ElementCollection
@CollectionTable(name="funcionario_telefone") //opcional, define o nome da tabela no banco
@MapKeyEnumerated(EnumType.STRING)
@MapKeyColumn(name="tipo_telefone") ///opcional indica qual coluna armazena a chave
@Column(name="numero") //nome da coluna que armazena o valor
private Map<TipoTelefone, String> telefones;
//...
}
Caso quisessemos usar como chave o tipo
java.util.Date, poderíamos usar a anotação
@MapKeyTemporal ao invés de
@MapKeyEnumerated.
3 Chave é um tipo básico e valor é uma entidade
Exemplo: Um departamento tem vários cubículos cada qual com um funcionário.
A modelagem tradicional
um departamento tem muitos funcionários ainda não atenderia eficazmente esse cenário. O software precisa indexar cubículo e funcionário.
@Entity
public class Departamento{
@Id private int id;
@ManyToMany
@JoinTable(name="DEPT_EMP", joinColumns=@JoinColumn(name="DEPT_ID"),
inverseJoinColumn=@JoinColumn(name="EMP_ID"))
@MapKeyColumn(name="CUB_ID")
private Map<String, Funcionario> funcionarioPorCubiculo;
}
4 Chave é o ID da própria entidade que representa o valor do Map
É comum e intuitivo indexar a chave do map pelo ID da entidade. No exemplo anterior o poderíamos usar algum atributo do funcionário como identificador do cubículo ao invés do próprio nome atribuído pelo departamento.
@Entity
public class Departamento{
@Id private int id;
@OneToMany(mappedBy="departamento")
@MapKey(name="id")
private Map funcionarios;
}
5 Chave é uma entidade e valor é um tipo básico
Exemplo: Atribuir a senioridade de cada funcionário no departamento em um contexto onde o funcionário pode atuar em vários departamentos e a senioridade podendo variar, uma vez que ela é determinada em função do departamento (um funcionário pode ser pleno no setor de desenvolvimento e junior no departamento de infraestrutura...).
@Entity
public class Department{
@Id private int id;
@ElementCollectionn
@CollectionTable(name="emp_senioridade")
@MapKeyJoinColumn(name="emp_id")
@Column(name="senioridade")
private Map<Employee, Integer> seniorites;
// ...
}