Esta é uma pergunta muito comum, portanto, esta resposta é baseada neste artigo que escrevi no meu blog.
Um para muitos
O relacionamento de uma para muitas tabelas é o seguinte:
Em um sistema de banco de dados relacional, um relacionamento de tabela um para muitos vincula duas tabelas com base em uma Foreign Key
coluna no filho que faz referência à Primary Key
linha da tabela pai.
No diagrama da tabela acima, a post_id
coluna da post_comment
tabela possui um Foreign Key
relacionamento com a coluna do post
ID da tabela Primary Key
:
ALTER TABLE
post_comment
ADD CONSTRAINT
fk_post_comment_post_id
FOREIGN KEY (post_id) REFERENCES post
Anotação @ManyToOne
A melhor maneira de mapear o relacionamento de uma para muitas tabelas é usar a @ManyToOne
anotação.
No nosso caso, a entidade filha PostComment
mapeia a post_id
coluna Chave estrangeira usando a @ManyToOne
anotação:
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
@Id
@GeneratedValue
private Long id;
private String review;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
}
Usando a @OneToMany
anotação JPA
Só porque você tem a opção de usar a @OneToMany
anotação, isso não significa que essa deve ser a opção padrão para todo relacionamento de banco de dados um para muitos . O problema com as coleções é que só podemos usá-las quando o número de registros filhos é bastante limitado.
A melhor maneira de mapear uma @OneToMany
associação é confiar no @ManyToOne
lado para propagar todas as alterações de estado da entidade:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
//Constructors, getters and setters removed for brevity
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
}
A entidade pai Post
, apresenta dois métodos utilitários (por exemplo, addComment
e removeComment
) que são usados para sincronizar os dois lados da associação bidirecional. Você deve sempre fornecer esses métodos sempre que estiver trabalhando com uma associação bidirecional, pois, caso contrário, corre o risco de problemas de propagação de estado muito sutis .
A @OneToMany
associação unidirecional deve ser evitada, pois é menos eficiente do que o uso @ManyToOne
ou a @OneToMany
associação bidirecional .
Para obter mais detalhes sobre a melhor maneira de mapear o @OneToMany
relacionamento com JPA e Hibernate, consulte este artigo .
Um a um
O relacionamento da tabela um para um é o seguinte:
Em um sistema de banco de dados relacional, um relacionamento de tabela um para um vincula duas tabelas com base em uma Primary Key
coluna no filho, que também é uma Foreign Key
referência à Primary Key
linha da tabela pai.
Portanto, podemos dizer que a tabela filho compartilha Primary Key
com a tabela pai.
No diagrama da tabela acima, a id
coluna na post_details
tabela também possui um Foreign Key
relacionamento com a coluna da post
tabela id
Primary Key
:
ALTER TABLE
post_details
ADD CONSTRAINT
fk_post_details_id
FOREIGN KEY (id) REFERENCES post
Usando o JPA @OneToOne
com@MapsId
anotações
A melhor maneira de mapear um @OneToOne
relacionamento é usar @MapsId
. Dessa forma, você nem precisa de uma associação bidirecional, pois sempre pode buscar a PostDetails
entidade usando o métodoPost
identificador entidade.
O mapeamento fica assim:
[code language = "java"] @Entity (name = "PostDetails") @Table (name = "post_details") classe pública PostDetails {
@Id
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;
public PostDetails() {}
public PostDetails(String createdBy) {
createdOn = new Date();
this.createdBy = createdBy;
}
//Getters and setters omitted for brevity
} [/ código]
Dessa forma, a id
propriedade serve como Chave Primária e Chave Externa. Você notará que a @Id
coluna não usa mais uma @GeneratedValue
anotação, pois o identificador é preenchido com o identificador da post
associação.
Para obter mais detalhes sobre a melhor maneira de mapear o @OneToOne
relacionamento com JPA e Hibernate, consulte este artigo .
Muitos para muitos
O relacionamento de tabela muitos para muitos é o seguinte:
Em um sistema de banco de dados relacional, um relacionamento de tabela muitos para muitos vincula duas tabelas pai por meio de uma tabela filha que contém duas Foreign Key
colunas referenciando as Primary Key
colunas das duas tabelas pai.
No diagrama da tabela acima, a post_id
coluna da post_tag
tabela também possui um Foreign Key
relacionamento com a coluna do post
ID da tabela Primary Key
:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_post_id
FOREIGN KEY (post_id) REFERENCES post
E, a tag_id
coluna na post_tag
tabela possui um Foreign Key
relacionamento com a coluna de tag
identificação da tabela Primary Key
:
ALTER TABLE
post_tag
ADD CONSTRAINT
fk_post_tag_tag_id
FOREIGN KEY (tag_id) REFERENCES tag
Usando o @ManyToMany
mapeamento JPA
É assim que você pode mapear o many-to-many
relacionamento da tabela com JPA e Hibernate:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
//Getters and setters ommitted for brevity
public void addTag(Tag tag) {
tags.add(tag);
tag.getPosts().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getPosts().remove(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post)) return false;
return id != null && id.equals(((Post) o).getId());
}
@Override
public int hashCode() {
return 31;
}
}
@Entity(name = "Tag")
@Table(name = "tag")
public class Tag {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
@ManyToMany(mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
//Getters and setters ommitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
- A
tags
associação na Post
entidade define apenas os tipos PERSIST
e MERGE
cascata. Conforme explicado neste artigo , a REMOVE
transição de estado da entidade não faz sentido para uma @ManyToMany
associação JPA, pois pode desencadear uma exclusão de cadeia que acabaria com os dois lados da associação.
- Conforme explicado neste artigo , os métodos do utilitário adicionar / remover são obrigatórios se você usar associações bidirecionais para garantir que os dois lados da associação estejam sincronizados.
- A
Post
entidade usa o identificador de entidade para igualdade, uma vez que não possui nenhuma chave comercial exclusiva. Conforme explicado neste artigo , você pode usar o identificador de entidade para igualdade, desde que ele se mantenha consistente em todas as transições de estado da entidade .
- A
Tag
entidade possui uma chave comercial exclusiva, marcada com a @NaturalId
anotação específica do Hibernate . Nesse caso, a chave comercial exclusiva é o melhor candidato para verificações de igualdade .
- O
mappedBy
atributo da posts
associação na Tag
entidade marca que, nesse relacionamento bidirecional, oPost
entidade possui a associação. Isso é necessário, pois apenas um lado pode possuir um relacionamento e as alterações são propagadas apenas para o banco de dados desse lado específico.
- O
Set
deve ser preferido, pois usar um List
com @ManyToMany
é menos eficiente.
Para obter mais detalhes sobre a melhor maneira de mapear o @ManyToMany
relacionamento com JPA e Hibernate, consulte este artigo .