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; }
}