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:
- Eu preciso saber quais os notebooks que determinada pessoa possui?
- 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
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo