Programador ajudante e aprendiz da comunidade open source.

Hibernate - Relacionamento ManyToMany Sem Atributos

Para aqueles que trabalham com Java, é quase que improvável a não utilização do framework de persitência Hibernate. Sabemos que ele é muito poderoso e trabalha de diversas formas, por isso irei mostrar, neste primeiro de 3 artigos, como trabalhar com um relacionamento que atormenta muitos desenvolvedores, o ManyToMany.

Objetivo:

Utilizando o Hibernate, será mostrado uma forma simples de criar uma tabela intermediária em um relacionamento ManyToMany que não possui atributos e estado.

Cenário:

Teremos uma tabela Perfil e uma tabela Acesso, onde poderemos ter um perfil com vários acessos e um acesso poderá estar em vários perfis.

Análise:

Quando estamos fazendo o nosso diagrama de classe e temos uma entidade A que pode estar relacionada a várias entidades B e esta entidade B pode estar relacionada a várias entidades A, temos um relacionamento muitos para muitos:

ManyToMany Simples

Este relacionamento requer uma nova entidade para representar uma tabela intermediária para manter o(s) relacionamento(s) da(s) chave(s) da entidade A com a(s) chave(s) da entidade B.

ManyToMany Full

Solução:

Reparem que a tabela intermediária AcessoPerfil não possui nada além das chaves estrangeiras da tabela Perfil e Acesso, ou seja, ela não possui estado já que nem agregaria valor pra gente manipula-lá diretamente. Sendo assim ela só sevirá para armazenar os relacionamentos. Com isso uma surrogate key (Chave Primária) se faz necessária neste caso.

Agora vamos criar as entidades para representar estas tabelas e seus relacionamentos.

Acesso.java

@Entity
public class Acesso {

 @Id
 @GeneratedValue
 private Long id;
 private String nome;

 // getters e setters

}

A entidade Acesso é bem simples, possuindo um nome e uma chave primária gerada automáticamente pelo banco.

Perfil.java

@Entity
public class Perfil {

 @Id
 @GeneratedValue
 private Long id;
 private String nome;

 @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
 @JoinTable(
  name = "AcessoPerfil",
  joinColumns = @JoinColumn(name = "perfil_id"), inverseJoinColumns = @JoinColumn(name = "acesso_id")
 )
 private Collection<Acesso> acessoList;

 // getters e setters

}

No Perfil também temos um nome e uma chave primária gerada pelo banco, porém também possui uma lista de Acessos, já que um perfil TEM vários acessos. É bem simples deduzir que aqui é o lugar da lista, mas podemos nos confundir na hora do mapeamento do Hibernate.

O erro mais comum é pensarmos no @OneToMany, já que um perfil tem vários acesso. Não podemos anotar esta lista assim, pois se pensarmos na nossa necessidade contrária, um acesso deve poder pertencer a vários perfis. Este erro fica mais claro quando tentamos salvar dois perfis distintos relacionado a um mesmo acesso, já que recebemos um erro do Hibernate dizendo que o código do acesso não é único na tabela intermediária, ou seja, acesso_id esta configurado como unique. E é assim que deveria ser, mesmo você querendo salvar vários acessos para um perfil. Veja que se você deixar um acesso pertencer a mais de um perfil, o relacionamento inverso (Acesso -> Perfil) deixará de ser @ManyToOne, já que ele estará ligado a mais de um perfil, e passará a ser @OneToMany. Oras, se precisamos de um relacionamento um para muitos em ambos os lados devemos utilizar o @ManyToMany para que seja criada a tabela intermediária para manter os relacionamentos das chaves e ai sim poderemos ter vários perfis relacionados a vários acessos e vice-verso.

O @ManyToMany deve estar configurado com cascade para PERSIST e MERGE que são as ações de salvar e atualizar, pois assim é possível o Hibernate salvar o Perfil e em uma ação cascateada salvar cada item da lista de acesso, que em nível de tabela é representado por uma chave na tabela intermediária. Não se preocupe com o REMOVE e os orphans, pois isso já é manipulado automáticamente.

O legal desse relacionamento many-to-many sem atributos é que não precisamos criar a tabela intermediária explícitamente, pois o Hibernate já cuida disso pra gente. Mas e se eu quisesse personalizar os nomes das FKs (Foreing Keys) e o nome da tabela? Bem, isso é possível através da anotação @JoinTable que possui um parâmetro chamado name no qual indica o nome da tabela a ser criada, que demos o nome de AcessoPerfil. Além disso podemos dar nome aos campos das FKs através de um outro atributo chamado joinColumns, que será o nome da coluna da tabela principal (Perfil), na qual você esta fazendo a anotação, e o atributo inverseJoinColumns que será o nome da chave da tabela secundária (Acesso). Temos um único detalhe na hora de dar o nome, que em vez de usarmos = "nome", precisamos de uma anotação para encapsular este nome chamada de @JoinColumn que também possui o atributo name. Sendo assim damos o nome da seguinte forma: = @JoinColumn(name = "nome").

Veja um exemplo de como seria o código para salvar dois perfis distintos onde ambos teriam os dois mesmos tipos de acessos.

Programa.java

public static void main(String[] args) {
  EntityManager manager = JPAHelper.getEntityManager();

  Acesso acesso1 = new Acesso();
  acesso1.setNome("acesso-1");
  acesso1 = manager.merge(acesso1);

  Acesso acesso2 = new Acesso();
  acesso2.setNome("acesso-2");
  acesso2 = manager.merge(acesso2);

  Collection<Acesso> acessoList = Arrays.asList(acesso1, acesso2);

  Perfil perfil1 = new Perfil();
  perfil1.setNome("perfil-1");
  perfil1.setAcessoList(acessoList);
  manager.merge(perfil1);

  Perfil perfil2 = new Perfil();
  perfil2.setNome("perfil-2");
  perfil2.setAcessoList(acessoList);
  manager.merge(perfil2);

  JPAHelper.close();
}

Neste código acima criamos dois acessos e os salvamos gerando as chaves número 1 e 2 na tabela Acesso. Em seguida colocamos esses dois acessos em uma lista para vinculá-la aos perfis. Então criamos dois perfis e dissemos que cada um deles estará relacionado com os dois acessos criados. Ao salvarmos os perfils teremos o seguinte resultado:

ManyToMany Table

Assim vemos que o perfil 1 tem o acesso 1 e 2, assim como o perfil 2 também possui o acesso 1 e 2. Deste modo basta alimentar a lista de acesso do perfil e o salvar que a propagação do relacionamento da tabela intermediária será automático.

Link do projeto:

http://github.com/wbotelhos/hibernate-manytomany-sem-atributos

  1. Fabio 14 Abr 2013 22:46

    Valeu pelo artigo Washington,
    Parabéns cara, mais claro que isso impossível!

    Tem como me dar uma dica de um bom livro de Hibernate em PT-br?
    Estou concluindo a Faculdade e pretendo usar Hibernate no projeto.
    O importante para mim é a didática, mais do que ser aquele livrão tipo Bíblia.

    Sucesso ai.

  2. RiachoMolhado 24 Fev 2012 16:37

    Cara, primeiramente obrigado por este artigo! me ajudou muito.

    Agora vem a dúvida: como implementar o método buscaPerfil(id) da classe PerfilDAO para popular o List de acessos?

    1. Washington Botelho autor 23 Mar 2012 12:09

      Oi RiachoMolhado,

      Neste caso você teria de fazer um findByIDs onde teria uma cláusula WHERE contendo todos os IDs que tornam o seu registro único.
      E é por essas e outras que evitamos a chave composta quando não necessário. ;/

  3. Adriano 30 Nov 2011 11:16

    Muito bom o artigo.Só uma duvida , como o hibernate sabe que a coluna acesso_id pertence a tabela acesso? Seria por causa do nome , tipo "acesso" = tabela , "_id" = a chave ?

    1. Washington Botelho autor 4 Dez 2011 11:43

      Oi Adriano,

      O Hibernate é voltado a objeto e esse campo acesso_id pra ele na verdade é o objeto Acesso navegando no atributo id.
      Veja que em nosso modelo temos o atributo Acesso acesso, logo pra ele acesso.id seria o mesmo que acesso.getId() no Java.
      Tudo feito em cima de convesão, por isso é mais façil trabalhar com Hibernate que o Plain SQL.

  4. Christiane 21 Nov 2011 12:31

    Estava procurando como fazer o ManyToMany e a cada leitura me confundia mais. Parabéns pela explicação clara e objetiva.

    1. Washington Botelho autor 26 Nov 2011 14:49

      Oi Christiane,

      Creio que semana que vem eu já publique a segunda parte do artigo.

      Muito obrigado. (:

  5. Jeff 7 Nov 2011 11:47

    Simplesmente perfeito, tava olhando em vários lugares mas ninguem conseguia explicar com tanta claresa.
    Grato.

    1. Washington Botelho autor 16 Nov 2011 14:48

      Oi Jeff,

      Muito obrigado.
      Continua seguindo o blog que terá mais dois posts sobre ManyToMany bem legais. (;

Em resposta:
(cancelar)
Formate seu código utilizando Markdown.