Mapeamento Objeto-Relacional (ORM): Dominando o @Entity, @Id e Relacionamentos no Spring Boot
O Mapeamento Objeto-Relacional (ORM) traduz as suas classes Java para tabelas no banco de dados. Neste post, vamos entender como isso acontece e dominar as anotações essenciais da JPA, desde o mapeamento básico com @Entity até o mapeamento de relacionamentos com @ManyToOne e @ManyToMany.
O abismo entre a Orientação a Objetos e o Banco Relacional
Se você já trabalhou com Java "puro" (usando JDBC), com certeza lembrr do ResultSet. Era necessário escrever a query SQL na mão, executar no banco, pegar o retorno, iterar linha por linha e preencher os atributos da classe Java um por um. Era um trabalho massante, repetitivo e propenso a erros.
O problema central é que Bancos de Dados Relacionais e a Orientação a Objetos falam línguas diferentes. No Java, nós temos classes, herança e listas de objetos. No banco de dados (PostgreSQL, MySQL), temos tabelas, colunas chaves primárias e chaves estrangeiras.
O Mapeamento Objeto-Relacional (ORM) nasceu para ser o tradutor simultâneo entre esses dois mundos. No ecossistema Java, temos a especificação a JPA (Jakarta Persistence API), e a ferramenta que faz o trabalho por trás dos panos é o Hibernate.
Para que o Hibernate saiba como traduzir a sua classe, precisamos colocar algumas anotações nela.
O Básico: Transformando Classes em Tabelas
Imagine que temos uma classe Produto. Para que o Spring saiba que ela deve virar uma tabela no banco de dados, usamos três anotações essenciais.
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "tb_produtos")
public class Produto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "nome_produto", nullable = false, length = 100)
private String nome;
private BigDecimal preco;
// Construtores, getters e setters...
}
Dissecando as anotações:
@Entity: Sem ela, o Hibernate simplesmente ignora a existência desse arquivo. Ela avisa: "Ei, está classe representa um registro no banco de dados".
@Table: Opcional, mas altamente recomendada. Sem ela, o banco criaria uma tabela chamada Produto (nome da classe). Com ela, nós forçamos o padrão de nomenclatura do banco, como tb_produtos. Também evita conflitos caso o nome da classe seja uma palavra reservada do banco (ex: User).
@Id e @GeneratedValue: Toda tabela relacional precisa de uma Chave Primária (Primary Key). O @Id marca o campo que será a chave, e o @GeneratedValue diz que não somos nós que vamos preencher esse número, e sim o próprio banco de dados (auto-incremento).
@Column: Se você não colocar nada (como no campo preco), a coluna terá exatamente o nome do atributo. O @Column serve para customizar o nome da coluna no banco, proibir valores nulos (nullable = false) ou limitar o tamanho do texto.
O Desafio: Mapeando Relacionamentos
No mundo real, tabelas não vivem isoladas. Elas se relacionam. E é no mapeamento das Chaves Estrangeiras que muitos desenvolvedores se confundem. Vamos entender os dois cenários mais comuns.
1. Muitos para Um @ManyToOne
Esta é uma relação muito comum em sistemas corporativos. Pense em um e-commerce: Muitos Pedidos podem pertencer a Um Cliente.
A regra de ouro do ORM é: A anotação @ManyToOne sempre fica na classe que vai receber a Chave Estrangeira (Foreign Key) no banco de dados. Neste caso, a tabela de Pedido é quem guarda o cliente_id.
@Entity
@Table(name = "tb_pedidos")
public class Pedido {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Muitos pedidos podem ser feitos por 1 Cliente
@ManyToOne
@JoinColumn(name = "cliente_id") // Nome da coluna da chave estrangeira
private Cliente cliente;
private LocalDate dataPedido;
}
Com isso, quando você buscar um Pedido no Java, o Hibernate automaticamente faz o JOIN e traz o objeto Cliente preenchido para você.
2. Muitos para Muitos @ManyToMany
Imagine um sistema escolar: Um Aluno pode estar matriculado em Muitos Cursos, e Um Curso possui Muitos Alunos.
Bancos de dados relacionais não suportam "Muitos para Muitos" de forma direta. Eles exigem a criação de uma terceira tabela no meio (a tabela associativa ou tabela de junção). Felizmente, a JPA faz isso para nós de forma transparente.
@Entity
@Table(name = "tb_alunos")
public class Aluno {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
@ManyToMany
@JoinTable(
name = "tb_aluno_curso", // Nome da tabela associativa (a tabela do meio)
joinColumns = @JoinColumn(name = "aluno_id"), // A chave da classe atual
inverseJoinColumns = @JoinColumn(name = "curso_id") // A chave da outra classe
)
private List<Curso> cursos = new ArrayList<>();
}
Ao usar o @JoinTable, o Hibernate cria sozinho a tabela tb_aluno_curso no banco de dados e gerencia a inserção dos IDs sempre que você adicionar um Curso na lista do Aluno no Java.
Conclusão: O poder exige responsabilidade
O ORM é uma das ferramentas que mais aumentam a nossa produtividade no ecossistema Spring Boot. Em vez de passarmos horas concatenando strings SQL, focamos exclusivamente nas regras de negócio e na manipulação dos objetos.
Porém, essa "mágica" cobra o seu preço se não for bem compreendida. Colocar um @ManyToMany em lugares errados ou esquecer de configurar o carregamento correto (o famoso problema do Lazy vs Eager) pode gerar centenas de queries desnecessárias, derrubando a performance do sistema (o problema do N+1).
Use as anotações a seu favor, mas nunca deixe de olhar os logs do Hibernate para entender o SQL que está sendo gerado por trás dos panos!