ManyToMany Hibernate: Variações Unidirecional e Bidirecional

Veja neste artigo como funciona o ManyToMany Unidirecional e Bidirecional, recurso importante para persistência de dados utilizando frameworks como Hibernate.

Em primeiro lugar é importante saber o que é um relacionamento @ManyToMany (em nossos exemplos usaremos o JPA, mas o conceito é o mesmo para outros frameworks como Hibernate, por exemplo). Este relacionamento dita que duas entidades relacionam-se entre si de forma múltipla, ou seja, a entidade A possui várias entidades B, assim como a entidade B possui várias entidades A. Partindo para um exemplo mais prático, imagine as seguintes situações para fixação do conceito:

a) Um usuário do sistema pode fazer parte de vários grupos e em um grupo pode haver diversos usuário. Neste caso temos um relacionamento N-N entre a entidade Usuario e a entidade Grupo.

b) Uma pessoa pode possuir vários notebooks, e um notebook pode possuir vários donos. Imagine uma família onde tudo é compartilhado: um notebook terá vários donos assim como um dono pode ter vários notebooks.

Unidirecional

Um relacionamento ManyToMany do tipo Unidirecional só pode ser identificado de um dos lados, por isso o nome que significa “Uma única direção”. No caso dos notebooks e seus donos, por exemplo, você precisa fazer duas perguntas para identificar se seu relacionamento deve ou não ser Unidirecional:

  1. Eu preciso saber quais os notebooks que determinada pessoa possui?
  2. Eu preciso saber quais os donos de determinado notebook?

Se uma das respostas para as perguntas acima for falsa, então você pode usar um relacionamento ManyToMany Unidirecional. Na Listagem 1 vemos o porque você deve usar esse tipo derelacionamento.

Listagem1. Criando a Entidade Pessoa

import java.util.List; //Using the * to make the import list smaller import javax.persistence.*; @Entity @Table(name = "Pessoa") @SecondaryTable(name = "health_care", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "id") }) public class Pessoa { @Id private int id; @Column private String name; @Column(table = "health_care", name = "company_name") private String companyName; @ManyToMany @JoinTable(name="pessoa_has_notebooks", joinColumns= {@JoinColumn(name="pessoa_id")}, inverseJoinColumns= {@JoinColumn(name="notebook_id")}) private List notebooks; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCompanyName() { return companyName; } public void setCompanyName(String companyName) { this.companyName = companyName; } public List getNotebooks() { return notebooks; } public void setNotebooks(List notebooks) { this.notebooks = notebooks; } }

A entidade criada na Listagem 1 faz referência as pessoas (donas dos notebooks). Perceba que nesta entidade temos uma lista de notebooks. Como se trata de uma relação N-N, será criada uma nova tabela que fará o relacionamento entre pessoa e notebook. O nome dessa tabela foi definido na anotação @JoinTable, e o nome dos campos foi definido nas anotações @JoinColumn. Fique atento no mapeamento ManyToMany que acontece em apenas um dos lados e tem o lado “dominante”, ou seja, apenas um dos lados pode afetar diretamente a tabela criada pelo @JoinTable.

Sendo assim, quando definimos o joinColumns estamos dizendo quem é o lado dominante, e quando definimos o inverseJoinColumns estamos definindo o lado dominado. Perceba que em nossa classe notebook não há nenhuma referência para a entidade Pessoa, isso porque estamos trabalhando com um relacionamento Unidirecional.

Na Listagem 2 vemos a criação da entidade notebook.

Listagem 2. Criando Entidade Notebook

//Using the * to make the import list smaller import javax.persistence.*; @Entity @Table(name="notebook") public class Notebook { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int id; private String serialNumber; private int ramMemoryTotal; private int hdSpaceTotal; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSerialNumber() { return serialNumber; } public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; } public int getRamMemoryTotal() { return ramMemoryTotal; } public void setRamMemoryTotal(int ramMemoryTotal) { this.ramMemoryTotal = ramMemoryTotal; } public int getHdSpaceTotal() { return hdSpaceTotal; } public void setHdSpaceTotal(int hdSpaceTotal) { this.hdSpaceTotal = hdSpaceTotal; } }

Bidirecional

Nesta etapa entram algumas características a mais em se tratando de relacionamentos ManyToMany. Veremos com mais clareza a diferença entre o lado dominado e o lado dominante. No tópico anterior ficou claro que o lado dominante é sempre o que possui o mapeamento ManyToMany, afinal é apenas a partir dele que podemos ter acesso ao mapeamento.

O que muda no relacionamento ManyToMany Bidirecional é que ambos os lados possuem o mapeamento ManyToMany, ou seja, você conseguirá as seguintes informações: quais os notebooks determinada pessoa tem e quais pessoas são donas de determinado notebook. Veja o exemplo da Listagem 3 que demonstra nossa classe Notebook com o relacionamento implementado.

Listagem 3. Classe Notebook com ManyToMany

//Using the * to make the import list smaller import javax.persistence.*; @Entity @Table(name="notebook") public class Notebook { @Id @GeneratedValue(strategy=GenerationType.AUTO) private int id; private String serialNumber; private int ramMemoryTotal; private int hdSpaceTotal; //Relacionamento implementado @ManyToMany(mappedBy="notebooks") private List pessoas; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSerialNumber() { return serialNumber; } public void setSerialNumber(String serialNumber) { this.serialNumber = serialNumber; } public int getRamMemoryTotal() { return ramMemoryTotal; } public void setRamMemoryTotal(int ramMemoryTotal) { this.ramMemoryTotal = ramMemoryTotal; } public int getHdSpaceTotal() { return hdSpaceTotal; } public void setHdSpaceTotal(int hdSpaceTotal) { this.hdSpaceTotal = hdSpaceTotal; } public List getPessoas() { return persons; } public void setPessoas(List pessoas) { this.persons = persons; } }

A nossa classe Pessoa não muda em nada, então temos agora duas classes, ambas com ManyToMany implementado. Porém, algo interessante aqui é a presença do atributo “mappedBy” presente na classe Notebook. Qual a função dele ?

É através desse atributo que dizemos que a classe Notebook é a dominada, ou seja, quem possui o “mappedBy” é o lado dominado/fraco, muito simples de assimilar. Isso porque o lado dominado só tem a função de mostrar as pessoas que são donas daquele notebook, mas não tem nenhum poder sobre a entidade pessoa. Vamos a um exemplo (Listagem 4) para ficar mais simples a compreensão, partindo do principio que a classe dominante é a Pessoa e a classe dominada sendo Notebook.

Listagem 4. Persistindo o relacionamento ManyToMany

//Using the * to make the import list smaller import javax.persistence.*; import java.util.*; public class MyApp { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello"); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); Notebook noteA = new Notebook(); noteA.setSerialNumber("A0123"); Notebook noteB = new Notebook(); noteB.setSerialNumber("B0123"); Notebook noteC = new Notebook(); noteC.setSerialNumber("C0123"); List notebooks = new ArrayList(); notebooks.add(noteA); notebooks.add(noteB); notebooks.add(noteC); Pessoa pessoa = new Pessoa(); pessoa.setName("Zorro"); pessoa.setNotebooks(notebooks); em.persist(pessoa); em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); e.printStackTrace(); } finally{ emf.close(); } System.out.println("It is over"); } }

O que você deve notar na Listagem 4 é que criamos vários notebooks e apenas uma pessoa, ou seja, setamos todos os notebooks dentro dessa pessoa, depois salvamos apenas a pessoa. Aqui entra o conceito de dominado e dominante: quando a Pessoa é o lado dominante, então ao salvar o objeto pessoa serão criados todos os objetos Notebook's e criar também o relacionamento entre Pessoa e Notebook. O contrário jamais acontecerá: não podemos criar vários objetos Pessoa, setar dentro de um notebook e salvar o notebook esperando que ele crie as Pessoas.

Com isso, o principal objetivo deste artigo foi demonstrar o uso do relacionamento ManyToMany Unidirecional e Bidirecional, mas ainda abrangemos conceitos importantes como o lado “dominante” e o lado “dominado” do relacionamento, que confunde muitos profissionais da área e acaba causando uma grande dor de cabeça.

Artigos relacionados