Capítulo 1: Perguntas de Entrevista — JPA & Banco de Dados
Este capítulo cobre as perguntas mais frequentes sobre JPA, Hibernate, relações entre entidades e problemas de performance em banco de dados. Cada pergunta inclui uma resposta simplificada e uma frase-chave para dizer na entrevista.
Q1. O que é o problema N+1 no JPA?
A analogia do restaurante
Imagine que você pede ao garçom no restaurante:
"Traga os 10 pedidos"
Depois, para cada pedido, você pergunta novamente:
"E quem é o cliente desse pedido?"
Você não faz 1 único pedido grande. Você faz:
- 1 pedido para os pedidos
- depois 10 pequenos pedidos para os clientes
Isso gera muitos acessos desnecessários.
O que é N+1 concretamente?
1 consulta para a lista principal
+ N consultas para os dados relacionados
────────────────────────────────
= N + 1 consultas no total
Se N = 10 pedidos:
SELECT * FROM orders; -- 1 consulta
SELECT * FROM customers WHERE id = 1; -- consulta 2
SELECT * FROM customers WHERE id = 2; -- consulta 3
SELECT * FROM customers WHERE id = 3; -- consulta 4
...
SELECT * FROM customers WHERE id = 10; -- consulta 11
Total = 11 consultas, sendo que uma só teria sido suficiente.
Por que isso acontece?
Porque muitas vezes a relação order.customer é carregada com lazy loading. O JPA não carrega o cliente no início — ele espera você chamar order.getCustomer().
O código que provoca o N+1
List<Order> orders = orderRepository.findAll();
for (Order o : orders) {
System.out.println(o.getCustomer().getName());
// ↑ CADA chamada dispara uma consulta SQL!
}
Aqui:
findAll()carrega os pedidos → 1 consultagetCustomer()dispara 1 consulta por pedido → N consultas
A solução: JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomers();
O JPA faz uma única consulta que traz tanto os pedidos quanto seus clientes.
Alternativa: @EntityGraph
@EntityGraph(attributePaths = {"customer"})
List<Order> findAll();
Alternativa: @BatchSize
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@BatchSize(size = 50)
private Customer customer;
}
Em vez de fazer N consultas, o JPA faz N/50 (carrega os clientes em lotes de 50).
Imagem mental simples
| Abordagem | Viagens |
|---|---|
| Sem correção (N+1) | 11 viagens para 10 pedidos |
| Com JOIN FETCH | 1 única viagem para tudo |
EN: "The N+1 problem happens when JPA fires one query per related entity. I fix it with JOIN FETCH or @EntityGraph to load everything in a single query."
PT: "O problema N+1 acontece quando o JPA executa uma consulta inicial, depois uma consulta extra para cada entidade relacionada. Eu resolvo com JOIN FETCH ou @EntityGraph para carregar tudo em uma única consulta."
Q2. Qual a diferença entre Lazy Loading e Eager Loading?
| Estratégia | Comportamento | Padrão para |
|---|---|---|
| LAZY | Carrega os dados relacionados apenas quando acessados | @OneToMany, @ManyToMany |
| EAGER | Carrega os dados relacionados imediatamente com a entidade | @ManyToOne, @OneToOne |
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
EAGER em tudo = desastre de performance. Se cada entidade carrega suas 5 relações em EAGER, uma simples consulta pode disparar centenas de consultas em cascata.
Boa prática: Tudo em LAZY + JOIN FETCH quando necessário.
"Eu defino tudo como LAZY loading por padrão e uso JOIN FETCH ou EntityGraph quando preciso carregar associações específicas de forma antecipada para um caso de uso."
Q3. Qual a diferença entre JpaRepository, CrudRepository e PagingAndSortingRepository?
JpaRepository (o mais completo)
↑ herda de
PagingAndSortingRepository (paginação + ordenação)
↑ herda de
CrudRepository (CRUD básico)
↑ herda de
Repository (interface marcador)
| Interface | Métodos adicionados |
|---|---|
| CrudRepository | save(), findById(), findAll(), deleteById(), count() |
| PagingAndSortingRepository | findAll(Pageable), findAll(Sort) |
| JpaRepository | flush(), saveAndFlush(), deleteInBatch(), retorna List<T> ao invés de Iterable<T> |
Na prática, quase sempre usamos JpaRepository.
Q4. Explique as estratégias de propagação do @Transactional
| Propagação | Comportamento |
|---|---|
REQUIRED (padrão) | Entra na transação existente ou cria uma nova |
REQUIRES_NEW | Sempre cria uma nova transação (suspende a existente) |
SUPPORTS | Entra se existir, senão executa sem transação |
NOT_SUPPORTED | Sempre executa sem transação |
MANDATORY | Transação existente obrigatória, senão lança exceção |
NEVER | Não pode haver transação, senão lança exceção |
NESTED | Transação aninhada (savepoint) |
Caso de uso concreto do REQUIRES_NEW
@Transactional
public void processPayment(Order order) {
paymentGateway.charge(order); // Transação principal
auditService.logPayment(order); // Se falhar, o pagamento ainda deve persistir
}
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logPayment(Order order) {
// Nova transação independente
// Mesmo se esta transação falhar, o pagamento está salvo
}
}
"Uso REQUIRED por padrão. Troco para REQUIRES_NEW quando preciso de uma transação independente, como logs de auditoria que devem persistir independente do resultado da transação principal."
Q5. Diferença entre @Transactional(readOnly = true) e @Transactional
@Transactional(readOnly = true) // Leitura: Hibernate desativa o dirty checking
public List<User> findAll() { ... }
@Transactional // Escrita: commit se sucesso, rollback se exceção
public User create(UserDto dto) { ... }
Boa prática: Colocar @Transactional(readOnly = true) no nível da classe, e @Transactional nos métodos de escrita:
@Service
@Transactional(readOnly = true)
public class UserService {
public List<User> findAll() { ... } // Herda readOnly = true
public User findById(Long id) { ... } // Herda readOnly = true
@Transactional // Override: escrita
public User create(UserDto dto) { ... }
@Transactional // Override: escrita
public void delete(Long id) { ... }
}
Q6. Como implementar paginação no Spring Data JPA?
// Repository — nada a fazer, JpaRepository suporta Pageable nativamente
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByRole(Role role, Pageable pageable);
}
// Controller
@GetMapping("/users")
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(defaultValue = "name") String sortBy) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy));
return userRepository.findAll(pageable);
}
O Page<T> retornado contém: conteúdo, número da página, tamanho, total de elementos, número de páginas.
Q7. O que são Query Methods no Spring Data JPA?
O Spring Data gera automaticamente as consultas SQL a partir do nome do método:
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
// → SELECT * FROM users WHERE name = ?
List<User> findByNameAndEmail(String name, String email);
// → SELECT * FROM users WHERE name = ? AND email = ?
List<User> findByAgeBetween(int min, int max);
// → SELECT * FROM users WHERE age BETWEEN ? AND ?
List<User> findByNameContainingIgnoreCase(String keyword);
// → SELECT * FROM users WHERE LOWER(name) LIKE LOWER('%keyword%')
Optional<User> findByEmail(String email);
// → SELECT * FROM users WHERE email = ? LIMIT 1
long countByRole(Role role);
// → SELECT COUNT(*) FROM users WHERE role = ?
boolean existsByEmail(String email);
// → SELECT CASE WHEN COUNT(*) > 0 THEN true ELSE false END FROM users WHERE email = ?
}
"O Spring Data JPA deriva consultas a partir dos nomes dos métodos. Para consultas complexas, uso @Query com JPQL ou SQL nativo."
Q8. Quando usar @Query vs Query Methods?
| Abordagem | Quando usar |
|---|---|
| Query Methods | Consultas simples (1-2 condições) |
| @Query (JPQL) | Consultas complexas, JOIN, agregações |
| @Query (native) | Consultas SQL específicas do SGBD, performance crítica |
// JPQL
@Query("SELECT u FROM User u WHERE u.department.name = :dept AND u.active = true")
List<User> findActiveByDepartment(@Param("dept") String dept);
// SQL nativo
@Query(value = "SELECT * FROM users WHERE created_at > NOW() - INTERVAL '7 days'",
nativeQuery = true)
List<User> findRecentUsers();
Q9. Como gerenciar relações entre entidades?
@ManyToOne / @OneToMany (a mais comum)
@Entity
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
}
@Entity
public class Customer {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}
@ManyToMany
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
Sempre use Set para @ManyToMany, nunca List — caso contrário, o Hibernate pode gerar consultas ineficientes (remove tudo e reinsere).
⬅️ Voltar ao Módulo 18 | Capítulo 2: Exercícios de Refatoração →