Para trabalharmos com banco de dados relacionados no Spring / JPA devemos conhecer algumas novas anotações Java:
@OneToMany
@ManyToOne
@OneToOne
@ManyToMany
Basicamente, essas anotações representam abstrações de relacionamentos criados no nosso banco de dados. Além disso, é preciso notar-se que cada um dos relacionamentos @OneToOne, @OneToMany, @ManyToOne e @ManyToMany pode ser unidirecional ou bidirecional.
O que isso significa?
Suponhamos duas entidades, A e B.
No relacionamento unidirecional entre duas entidades A e B, partindo-se da entidade A, eu chego facilmente a uma instância da entidade B, mas não consigo facilmente fazer o caminho contrário.
Já no relacionamento bidirecional, eu também posso a partir da entidade B, facilmente navegar de volta para a entidade A.
Para entendermos melhor, vamos supor o exemplo:
Com este diagrama podemos perceber que existe um relacionamento de Faculdade e Aluno. A entidade Faculdade possui um atributo de alunos : List<Aluno> e a entidade Aluno uma relação para faculdade, através do atributo faculdade_id : Int . Logo, este diagrama permite definir que uma faculdade possui vários alunos e um aluno só poderá estar em uma faculdade. Sendo assim, como podemos escrever esses relacionamentos usando o Spring e JPA?
Mapeamento @ManyToOne unidirecional
O @ManyToOne significa muitos-para-1. Neste seu exemplo (vamos supor que o campo faculdade na tabela Aluno deveria se chamar faculdade_id), teríamos isso:
Aluno
@Entity
public class Aluno {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
@ManyToOne
@JoinColumn(name="faculdade_id", nullable=false)
private Faculdade faculdade;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Faculdade getFaculdade() {
return faculdade;
}
public void setFaculdade(Faculdade faculdade) {
this.faculdade = faculdade;
}
}
O lado Many é o da classe que envolve isso tudo, no caso Aluno. O lado One é o da entidade relacionada, no caso Faculdade. Ou seja, muitos alunos para uma faculdade. A mesma regra se aplica também ao @ManyToOne, @OneToOne e @ManyToMany (veremos mais sobre eles abaixo).
Isso acontece porque um aluno tem apenas uma faculdade, mas uma faculdade pode ter muitos alunos. Com esse mapeamento, podemos fazer isso:
Aluno a = ...;
Faculdade f = a.getFaculdade();
Mapeamento @OneToMany unidirecional
O @OneToMany é o oposto do que o @ManyToOne, ou seja é o 1-para-muitos. Por exemplo, poderíamos fazer isso:
Faculdade
@Entity
public class Faculdade {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany
@JoinColumn(name = "faculdade_id") // Esta coluna está na tabela "aluno".
private List<Aluno> alunos;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Aluno> getAlunos() {
return alunos;
}
public void setAlunos(List<Aluno> alunos) {
this.alunos = alunos;
}
}
Com esse mapeamento, podemos fazer isso:
Faculdade f = ...;
List<Aluno> alunos = f.getAlunos();
Mapeamentos @OneToMany e @ManyToOne bidirecionais
Se você tiver os dois casos acima ao mesmo tempo, onde a partir de Aluno eu chego em faculdade e a partir de Faculdade eu chego em Aluno, o resultado vai ser que o mapeamento vai dar errado. Por quê? Porque o JPA verá dois mapeamentos distintos, um deles de Aluno para Faculdade e um outro mapeamento diferente de Faculdade para Aluno. Ocorre que esses dois mapeamentos são um só!
É aí que entra o campo mappedBy:
Aluno
@Entity
public class Aluno {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
@ManyToOne
@JoinColumn(name="faculdade_id", nullable=false)
private Faculdade faculdade;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Faculdade getFaculdade() {
return faculdade;
}
public void setFaculdade(Faculdade faculdade) {
this.faculdade = faculdade;
}
}
Faculdade
@Entity
public class Faculdade {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany(mappedBy="faculdade")
private List<Aluno> alunos;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Aluno> getAlunos() {
return alunos;
}
public void setAlunos(List<Aluno> alunos) {
this.alunos = alunos;
}
}
Nesta relação bidirecional, o mappedBy diz que o outro lado da relação que é o dono dela e que o campo que a modela é o de nome faculdade. Observe que esse é o nome do campo da classe Java, e não o nome do campo no banco de dados!
Em geral, é recomendável que o lado da relação que termine com o toOne seja o dono da relação.
Mapeamento @OneToOne
Se você usar o @OneToOne você modela o caso 1-para-1. Você pode fazer com que uma avaliação pertença a apenas um Aluno, mas nesse tipo de relacionamento, você também tem que um aluno só pode ter uma avaliação.
Você faria isso assim:
Avaliacao
@Entity
public class Avaliacao{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
@OneToOne
@JoinColumn(name = "aluno_id")
private Aluno aluno;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Aluno getAluno() {
return aluno;
}
public void setAluno(Aluno aluno) {
this.aluno= aluno;
}
}
Com isso, você pode fazer isso:
Avaliacao a = ...;
Aluno avaliado = a.getAluno();
Para fazer o contrário, é necessário que o relacionamento seja bidirecional:
Aluno
@Entity
public class Aluno {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String email;
@OneToOne(mappedBy = "aluno")
private Avaliacao avaliacao;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Avaliacao getAvaliacao() {
return avaliacao;
}
public void setAvaliacao(Avaliacao avaliacao) {
this.avaliacao = avaliacao;
}
}
E então, tendo o relacionamento bidirecional:
Aluno a = ...;
Avaliacao av = a.getAvaliacao();
Mapeamento @ManyToMany
Dado o problema apresentado acima, vamos supor o cenário:
Um curso tem várias disciplinas...
E, uma disciplina pode fazer parte de vários cursos
E vamos supor que tenhamos a tabela curso, a tabela disciplina e uma tabela intermediária curso_disciplina, onde cada linha contém a chave das outras duas tabelas.
Curso
@Entity
public class Curso {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
@ManyToMany
@JoinTable(
name = "curso_disciplina",
joinColumns = @JoinColumn(name = "curso_id"),
inverseJoinColumns = @JoinColumn(name = "disciplina_id"),
)
private List<Disciplina> disciplinas;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Disciplina> getDisciplinas() {
return disciplinas;
}
public void setDisciplinas(List<Disciplina> disciplinas) {
this.disciplinas = disciplinas;
}
}
A anotação @JoinTable é responsável por fazer o mapeamento da tabela intermediária. O joinColumns representa o lado da entidade que é dona do relacionamento (Curso) e o inverseJoinColumns o lado da entidade relacionada (Disciplina). Com isso tudo, é possível então fazer-se isso:
Curso c = ...;
List<Disciplina> disciplinas = c.getDisciplinas();
Para fazer o relacionamento bidirecional, novamente temos o mappedBy:
Curso
@Entity
public class Disciplina{
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
@ManyToMany(mappedBy = "disciplinas")
private List<Curso> cursos;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Curso> getCursos() {
return cursos;
}
public void setDisciplinas(List<Curso> cursos) {
this.cursos = cursos;
}
}
E então podemos fazer isso também:
Disciplina d = ...;
List<Curso> cursos = d.getCuros();
Desta forma, o próprio JPA no Spring criará este relacionamento entre as entidades no banco de dados!
No entanto, para o Spring entender este relacionamento na Controller e View, basta fazermos a associação ao objeto faculdade, de aluno. Exemplo:
Controller
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index() {
ModelAndView mav = new ModelAndView("index");
mav.addObject("alunos", alunoService.getAllAlunos());
return mav;
}
View
<html>
<head>
<title>CRUD Aluno</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
</style>
</head>
<body>
<a href="insert">Inserir novo</a> | <a href="read">Ver todos</a> <br> <br>
<table style="width:100%">
<tr>
<th>ID</td>
<th>Nome</td>
<th>E-mail</td>
<th>Faculdade</td>
<th>Excluir?</td>
<th>Alterar?</td>
</tr>
<tr th:each="aluno : ${alunos}">
<td th:text="${aluno.id}"></td>
<td th:text="${aluno.name}"></td>
<td th:text="${aluno.email}"></td>
<td th:text="${aluno.faculdade.name}"></td>
<td><a th:href="@{/delete(id=${aluno.id})}">Deletar</a></td>
<td><a th:href="@{/update(id=${aluno.id})}">Alterar</a></td>
</tr>
</table>
</body>
</html>