💻 Exemplos: Capítulo 06: Relacionamentos JPA (@OneToMany, @ManyToOne) e FetchType
Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — @ManyToOne LAZY e @OneToMany Bidirecional com Cascade
Modelo Pedido/ItemPedido completo: o lado dono (ItemPedido) mapeia a FK com LAZY; o lado inverso (Pedido) usa mappedBy, cascade = ALL e orphanRemoval = true, e o método adicionarItem mantém os dois lados sincronizados em memória.
package com.empresa.api.model;
import jakarta.persistence.*;
@Entity
public class ItemPedido {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pedido_id", nullable = false)
private Pedido pedido;
private Integer quantidade;
protected ItemPedido() {}
public ItemPedido(Integer quantidade) {
this.quantidade = quantidade;
}
void setPedido(Pedido pedido) { this.pedido = pedido; }
public Integer getQuantidade() { return quantidade; }
}
package com.empresa.api.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Pedido {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "pedido", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemPedido> itens = new ArrayList<>();
public void adicionarItem(ItemPedido item) {
itens.add(item);
item.setPedido(this);
}
public void removerItem(ItemPedido item) {
itens.remove(item); // orphanRemoval dispara o DELETE no próximo flush
}
public List<ItemPedido> getItens() { return itens; }
}
💻 Código de Exemplo 02 — Resolvendo N+1 com @EntityGraph e JOIN FETCH
Duas estratégias equivalentes para carregar Pedido junto de itens em uma única consulta SQL, evitando disparar um SELECT adicional por pedido ao iterar a lista.
package com.empresa.api.repository;
import com.empresa.api.model.Pedido;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
// Estratégia 1: @EntityGraph — declarativo, sem escrever JPQL.
@EntityGraph(attributePaths = {"itens"})
List<Pedido> findAll();
// Estratégia 2: JOIN FETCH explícito — mais controle sobre a query gerada.
@Query("SELECT DISTINCT p FROM Pedido p LEFT JOIN FETCH p.itens WHERE p.id = :id")
Optional<Pedido> buscarComItensPorId(@Param("id") Long id);
}
package com.empresa.api.service;
import com.empresa.api.model.Pedido;
import com.empresa.api.repository.PedidoRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PedidoService {
private final PedidoRepository repository;
public PedidoService(PedidoRepository repository) {
this.repository = repository;
}
@Transactional(readOnly = true)
public Pedido buscarDetalhado(Long id) {
// Acesso a "itens" aqui é seguro: ainda dentro da transação, sem LazyInitializationException.
return repository.buscarComItensPorId(id)
.orElseThrow(() -> new IllegalArgumentException("Pedido não encontrado: " + id));
}
}
💻 Código de Exemplo 03 — @ManyToMany com Tabela de Junção Nomeada
package com.empresa.api.model;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "usuarios")
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
@ManyToMany
@JoinTable(
name = "usuarios_perfis",
joinColumns = @JoinColumn(name = "usuario_id"),
inverseJoinColumns = @JoinColumn(name = "perfil_id")
)
private Set<Perfil> perfis = new HashSet<>();
public void adicionarPerfil(Perfil perfil) {
perfis.add(perfil);
perfil.getUsuarios().add(this);
}
}
package com.empresa.api.model;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Perfil {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nome;
@ManyToMany(mappedBy = "perfis") // lado inverso: não gera coluna adicional
private Set<Usuario> usuarios = new HashSet<>();
public Set<Usuario> getUsuarios() { return usuarios; }
}