Programador ajudante e aprendiz da comunidade open source.

Hibernate - Relacionamento ManyToMany Com Atributos

Neste post será mostrado como utilizar o relacionamento ManyToMany do Hibernate com uma maior autonomia de alteração da tabela intermediária. Sendo assim teremos um pouquinho mais de trabalho, porém uma maior flexibilidade. Se você não esta familiarizado com o Hibernate, recomendo a leitura do post Hibernate – Relacionamento ManyToMany Sem Atributos, pois este post esta sendo tratado com uma evolução do anterior.

Objetivo:

Utilizando o Hibernate, será mostrado como criar uma tabela intermediária que possui atributos e estado em um relacionamento ManyToMany.

Cenário:

Teremos as entidades Filme e Artista, onde podemos ter um filme com vários artistas e um artista poderá estar em vários filmes, sendo que o artista terá diferentes nomes em cada um destes filmes.

Análise:

Filme x Artista

A princípio temos a tabela Filme tendo vários artistas e a tabela Artista tendo vários filmes. Logo será necessário criar a tabela intermediária ArtistaFilme. Também precisamos de achar um lugar para mantermos o nome artístico do artista. Mas onde?

Filme x Artista x ArtistaFilme

Bem, aqui temos a nossa já conhecida tabela intermediária e como tínhamos de achar um lugar para o nome do artista para um filme específico, nada mais justo que deixar na tabela intermediária. Assim aquele nome só será válido para um artista em um determinado filme.

O que não tínhamos visto ainda era essa tabela intermediária com ID e muito menos outros atributos. O atributo nome já sabemos que era necessário isolá-lo lá para ser exclusivo do relacionamento, mas e o ID? Bem se essa tabela tem um ID ela possui estado, ou seja, ela é salva, consultada, alterada e removida, não apenas uma tabela automática para manter os relacionamentos. E como fazer o Hibernate criar essa tabela pra mim? Como diz meu amigo Bruno Oliveira (AbstractJ): não repita essa pergunta alto.

Perceba que o relacionamento no final das contas fica (Filme 1 -- * ArtistaFilme) e (Artist 1 -- * ArtistaFilme), além disso a tabela ArtistaFilme possui estado. Tá, vou deixar você falar a solução: "Putz, isso ai é um OneToMany no Filme e no Artista!". :D

Pois é, agora basta tratarmos a tabela intermediária como uma entidade da nossa aplicação e fazermos os relacionamentos:

Filme.java

@Entity
public class Filme {

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

  @OneToMany(mappedBy = "filme", cascade = CascadeType.ALL)
  private Collection<ArtistaFilme> artistaFilmeList;

  // getters e setters

}

Agora em vez de pensarmos que o filme terá vários artistas, iremos pensar que ele terá um elenco, ou seja, uma lista de ArtistaFilme. Pra isso utilizamos o @OneToMany que deverá estar como CascadeType.ALL para que quando salvarmos, atualizarmos ou removermos o filme, também seja feita esta alterações na tabela ArtistaFilme.

Como o OneToMany é a parte fraca do relacionamento por não possuir a chave estrangeira (Foreing Key), precisamos dizer como ele será vinculado à outra tabela. Para isso utilizamos o mappedBy com o nome do atributo da tabela ArtistaFilme que representará ele como objeto, que no banco será a chave estrangeira.

Artista.java

@Entity
public class Artista {

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

  @OneToMany(mappedBy = "artista", cascade = CascadeType.ALL)
  private Collection<ArtistaFilme> artistaFilmeList;

  // getters e setters

}

Sem muitos segredos, a entidade Artista utilizará as mesma configurações da entidade Filme, trocando apenas o nome do atributo que o representará na tabela ArtistaFilme, que neste caso será "artista".

ArtistaFilme.java

@Entity
public class ArtistaFilme {

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

  @ManyToOne
  @JoinColumn(name = "filme_id")
  private Filme filme;

  @ManyToOne
  @JoinColumn(name = "artista_id")
  private Artista artista;

  // getters e setters

}

Nesta tabela intermediária teremos o ID, já que agora ela possui estado e temos o nome dado ao artista no terminado filme. Filmes este, que esta representado pelo objeto "filme" que possui o relacionamento ManyToOne, pois ele será chave estrangeira (FK) nessa tabela. Como já conhecemos, utilizamos a anotação @JoinColumn para dar um nome bonito para a nossa FK.

Da mesma forma temos o objeto "artista" também representado a FK do Artista formando a identificação completa do relacionamento: um artista estréia em um filme com um determinado nome. Veja um exemplo a seguir de como seria o código para salvar um filme com dois artistas em seu elenco.

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

  Artista artista1 = new Artista();
  artista1.setNome("artista-1");
  artista1 = manager.merge(artista1);

  Artista artista2 = new Artista();
  artista2.setNome("artista-2");
  artista2 = manager.merge(artista2);

  Filme filme = new Filme();
  filme.setNome("filme");

  ArtistaFilme artistaFilme1 = new ArtistaFilme();
  artistaFilme1.setFilme(filme); // Referencia de memoria.
  artistaFilme1.setArtista(artista1);
  artistaFilme1.setNome("nome-artistico-1");

  ArtistaFilme artistaFilme2 = new ArtistaFilme();
  artistaFilme2.setFilme(filme); // Referencia de memoria.
  artistaFilme2.setArtista(artista2);
  artistaFilme2.setNome("nome-artistico-2");

  Collection<ArtistaFilme> elenco = Arrays.asList(artistaFilme1, artistaFilme2);

  filme.setArtistaFilmeList(elenco);

  manager.merge(filme);

  JPAHelper.close();
}

Neste código acima criamos dois artistas e os salvamos. Depois criamos um filme e o deixamos em memória. Em seguida criarmos o relacionamento entre o filme e o artista que terá um nome artístico nesta estréia. Pegamos estes dois artistas já relacionados ao filme e os colocamos em uma lista que forma o elenco. Este elenco é setado no filme a ser salvo que através da ação de cascada também salvará o elenco.

Repare que antes mesmo do filme ser salvo já passamos a referência de memória de seu objeto para o objeto ArtistaFilme. O filme ainda não tem chave primária, porém ao ser salvo e gerado sua chave, toda o elenco que será salvo por cacada já estará apontando para este filme. Lembre-se que o Hibernate trabalho orientado a objeto e não a tabela do banco.

Arista x Filme x ArtistaFilme

Assim vemos que o filme tem em seu elenco os artista 1 e 2. Deste modo basta alimentar esta lista os mesmos serão salvos na tabela intermediária como elenco.

Link do projeto:

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

  1. Junior 19 Set 2016 22:54

    Eu tive que comentar, puta que pariu (desculpe a expressão) que tutorial foda (e denovo)!

  2. Raphael Araújo 12 Out 2015 22:26

    Como faço pra chamar os getters, nesse caso. Por exemplo em uma JSP:

    filmes.artistaFilme.nome

    Sendo que o artistaFilme é uma lista e eu quero pegar o nome relacionado ao filme.

    Obrigado!

    1. Álamo 11 Nov 2015 14:23

      Raphael Araújo, nesse caso você terá que iterá na lista no seu jsp. Você pode usar tag jstl c:for para isso.

  3. Emilia 16 Fev 2015 08:25

    Fixe este pos me ajudou bastante. Estava a fazer uma base de dados com classes similares em que a relacao tambem era de muitos para muitos.

  4. Gabriel 4 Dez 2014 18:35

    Oi Botelho,

    kra, eu to com uma dúvida.

    Citando seu exemplo, imagine que eu quero gerar uma nova tabela filme-diretor. Então eu gero a classe filme-diretor, a classe diretor, adiciono uma nova collection na classe filme e adiciono uma collection na classe diretor.

    O problema é que quando eu faço isso, o hibernate da o seguinte erro:
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:325)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2149)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:78)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:68)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4126)
    at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:503)
    at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:468)
    at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:213)
    at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:275)
    at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1070)
    at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:989)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:716)
    at org.hibernate.type.EntityType.resolve(EntityType.java:502)
    at org.hibernate.type.ComponentType.resolve(ComponentType.java:666)

    para cada tentativa de inserção na tabela filme-diretor.

    Pode me ajudar com isso? Já procurei uma resposta na documentação e em outros lugares mas não encontrei nada.

  5. Renato 22 Ago 2012 14:16

    Opa beleza, eu estou com uma duvida, tenho uma modelagem bem parecida com essa entao irei explicar usando as tabelas deste tutorial como exemplo mesmo.
    Digamos que a tabela filme ja estivesse preenchida, ou seja, eu nao precisaria inserir dados nela, apenas consultar os dados, como funcionaria?

  6. Rodolfo 12 Dez 2011 12:18

    Washington,

    Você pode me ajudar, estou com um problema ao tentar criar um processo em minha aplicação.
    Estou usando o VRAPTOR e estou criando um JOB via quartz que vai enviar e-mails, porém meu Job precisa acessar um DAO @Component. Como eu resolvo isto?

    Uma outra duvida também, como acesso um DAO do Vraptor via servlet?

    Obrigado,
    Att. Rodolfo

    1. Washington Botelho autor 18 Dez 2011 01:18

      Oi Rodolfo,

      Você precisaria de estudar o conceito o Quartz pra isso, pois o acesso ao DAO é feito normalmente como se fosse uma classe qualquer.
      Mas o ideal seria você criar uma interface com as assinaturas desse seu DAO, injetar essa interface em seu controller e usá-la.
      Tenta usar esse plugin do VRaptor aqui: https://github.com/wpivotto/vraptor-tasks

      Se você estiver usando o VRaptor não deveria usar Servlet, pois o VRaptor abstrai exatamente ele.
      Nunca tentei fazer um acesso direto de um, pois realmente não faz muito sentido.

  7. Henrique Luz 6 Dez 2011 08:16

    Fala, cara.

    Parabéns pelo post. Bem fácil de entendê-lo. Ótima dica para quem ainda está se familiariazando com este poderoso framework que é o Hibernate.

  8. Rafael Ponte 6 Dez 2011 08:04

    Fala Botelhos,

    Excelente post, muito didático por sinal.

    O Mauricio Linhares escreveu um post muito bom sobre o mesmo assunto, porém mais voltado a modelo de domínio do que o mapeamento em si. Vale a pena conferir, http://alinhavado.wordpress.com/tutoriais/de-many-to-many-para-many-to-one-com-jpa/

    Um abraço.

    1. Washington Botelho autor 6 Dez 2011 11:40

      Fala Ponte,

      Eu li o artigo dele "denovo", inclusive tinha um comentário meu lá de que eu nem lembrava rs...

      A primeira parte deste artigo foi mais didática explicando o MER, esta segunda parte já ataquei a implementação.
      A próxima será com chaves compostas. (:

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