Tuesday, July 4, 2017

Casos de Mapeamento de Coleções no JPA

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;
   // ...
}

No comments:

Post a Comment