Módulo Back-end: JPA, Consultas SQL e JPQL 🐘
Este capítulo aprofunda o uso da JPA (Jakarta Persistence API), abordando o ciclo de vida das entidades, estratégias para otimizar o acesso a dados e as diferentes formas de se realizar consultas, desde as mais simples até as mais complexas com SQL e JPQL.
Gerenciamento de Entidades com JPA
Sessão JPA e EntityManager
A JPA gerencia as entidades do sistema durante o que chamamos de sessão JPA. Uma sessão corresponde ao contexto em que a JPA realiza operações no banco de dados.
O objeto central para isso é o EntityManager, que encapsula a conexão com o banco e gerencia o estado das entidades.
- JPA “Raiz” (Nativa): O controle é manual. O desenvolvedor precisa criar o
EntityManagera partir de umEntityManagerFactorye gerenciar as transações explicitamente. - Spring Data JPA: O processo é abstraído. O Spring gerencia o
EntityManagere as transações para nós, geralmente através da anotação@Transactionale do uso de Repositórios.
| JPA Raiz (Manual) | Spring Data JPA (Automatizado) |
|---|---|
java EntityManagerFactory emf = ...; EntityManager em = emf.createEntityManager(); Product prod = new Product(); em.getTransaction().begin(); em.persist(prod); em.getTransaction().commit(); | ```java |
| @Autowired | |
| private ProductRepository repository; |
@Transactional public void meuMetodo() { Product prod = new Product(); repository.save(prod); }
### Estados de uma Entidade
Uma entidade gerenciada pela JPA passa por diferentes estados durante seu ciclo de vida:

* **Transient (Transiente)**: Um objeto recém-criado que ainda não foi associado à sessão JPA. Ele não tem representação no banco de dados.
* **Managed (Gerenciado)**: O objeto foi associado à sessão JPA (após um `persist` ou `find`). Todas as alterações feitas neste objeto serão sincronizadas com o banco de dados ao final da transação.
* **Detached (Desanexado)**: O objeto já foi gerenciado, mas a sessão JPA foi fechada. As alterações feitas nele não serão mais sincronizadas automaticamente.
* **Removed (Removido)**: Uma entidade gerenciada que foi marcada para remoção do banco de dados (após um `remove`).
### Salvando Entidades Associadas
Ao salvar uma entidade que se relaciona com outra, podemos passar a referência do objeto associado no corpo da requisição JSON.
* **Relacionamento Para-Um (Ex: Pessoa e Departamento)**

Para associar uma nova `Pessoa` a um `Departamento` existente, basta passar o ID do departamento no JSON.
```json
POST http://localhost:8080/people
{
"name": "Nova Pessoa",
"salary": 8000.0,
"department": {
"id": 1
}
}
```
* **Relacionamento Para-Muitos (Ex: Produto e Categorias)**

Para associar um novo `Produto` a várias `Categorias` existentes, passamos uma lista de objetos contendo os IDs das categorias.
```json
POST http://localhost:8080/products
{
"name": "Produto novo",
"price": 1000.0,
"categories": [
{ "id": 2 },
{ "id": 3 }
]
}
```
## Performance e Carregamento de Dados
### O Problema das Consultas Desnecessárias (N+1)
O grande vilão de performance em aplicações que usam JPA são as **idas e vindas desnecessárias ao banco de dados**. A causa mais comum é o carregamento tardio (*lazy loading*) de entidades associadas, que leva ao famoso **problema N+1 consultas**.
Isso ocorre quando você busca N entidades e, depois, para cada uma delas, faz uma nova consulta para buscar um dado associado, resultando em N+1 consultas totais em vez de apenas uma ou duas.
### Carregamento Padrão: EAGER vs. LAZY
Por padrão, a JPA define o tipo de carregamento (fetch) das associações da seguinte forma:
* Relacionamentos **Para-Um** (`@ManyToOne`, `@OneToOne`): **EAGER** (Ansioso). O dado associado é carregado imediatamente junto com a entidade principal.
* Relacionamentos **Para-Muitos** (`@OneToMany`, `@ManyToMany`): **LAZY** (Preguiçoso). Os dados associados (a coleção) só são carregados do banco quando são efetivamente acessados no código.
### Estratégias para Otimizar Consultas
1. **Atributo Fetch (Não Recomendado)**: É possível alterar o comportamento padrão diretamente na anotação (ex: `@ManyToOne(fetch = FetchType.LAZY)`). **Cuidado**: esta abordagem deve ser evitada, pois a mudança afeta a entidade globalmente e pode gerar efeitos colaterais indesejados em outras partes do sistema.
2. **Cláusula `JOIN FETCH` (Ideal)**: A melhor forma de resolver o problema N+1 é instruir a JPA a buscar os dados associados na mesma consulta usando `JOIN FETCH`.
```java
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// Busca os funcionários e já carrega os departamentos associados na mesma consulta SQL
@Query("SELECT obj FROM Employee obj JOIN FETCH obj.department")
List<Employee> findEmployeesWithDepartments();
}
```
> **Nota:** A cláusula `JOIN FETCH` não funciona para buscas paginadas no Spring Data JPA.
### Cache de Primeiro Nível e Transações
* **Cache em Memória**: A JPA mantém um "cache de primeiro nível" (mapa de identidade) das entidades gerenciadas. Se você busca uma entidade e precisa dela novamente na mesma sessão, a JPA a retorna da memória em vez de consultar o banco de dados novamente.
* **`@Transactional` e `open-in-view`**:
* A anotação `@Transactional` do Spring assegura que a transação com o banco de dados seja resolvida corretamente ao final do método e que todas as pendências "lazy" sejam carregadas enquanto a transação estiver ativa.
* A propriedade `spring.jpa.open-in-view=false` (recomendado) faz com que a sessão JPA seja encerrada assim que os dados saem da camada de serviço, prevenindo o carregamento lazy na camada de controller.
## Consultas Customizadas com Spring Data JPA
### Query Methods
O Spring Data JPA permite criar consultas customizadas apenas declarando um método no repositório com um nome específico.
* **Polêmica**: Vale a pena usar?
* Para consultas **muito simples**: Sim, é prático. (Ex: `findByName(String name)`).
* Para consultas **complexas**: É melhor escrever a consulta manualmente para ter mais clareza e controle.
### JPQL (JPA Query Language)
JPQL é a linguagem de consulta específica da JPA. Ela é muito parecida com SQL, mas opera sobre as **entidades e seus atributos** em vez de tabelas e colunas.
| SQL | JPQL |
| :--- | :--- |
| `SELECT * FROM tb_employee WHERE UPPER(name) LIKE 'MARIA%'` | `SELECT obj FROM Employee obj WHERE UPPER(obj.name) LIKE 'MARIA%'` |
| `SELECT tb_employee.* FROM tb_employee INNER JOIN tb_department ON ... WHERE tb_department.name = 'Financeiro'` | `SELECT obj FROM Employee obj WHERE obj.department.name = 'Financeiro'` |
* **Vantagens da JPQL**: Pode simplificar consultas (especialmente as que envolvem navegação entre objetos), integra-se bem com recursos do Spring Data JPA (como paginação) e retorna objetos já gerenciados pela JPA.
* **Desvantagens da JPQL**: Consultas complexas podem se tornar difíceis de escrever e validar, e existe uma curva de aprendizado para uma tecnologia específica.
### SQL Nativo (Raiz)
Para consultas muito complexas, que usam recursos específicos do banco de dados ou que são mais fáceis de testar diretamente no cliente SQL, usar SQL nativo é a melhor opção.
## Exercícios Práticos com SQL 🏋️
### Preparação do Ambiente PostgreSQL
Para praticar, você pode configurar um banco de dados PostgreSQL de duas formas:
1. **Instalação Direta**: Instale o PostgreSQL (versão 12, 13 ou 14) e um cliente de banco de dados como pgAdmin ou DBeaver.
2. **Docker**: Utilizar uma imagem Docker do PostgreSQL, o que facilita a criação de ambientes isolados.
### Exercícios Recomendados (Beecrowd)
A plataforma Beecrowd (antigo URI Online Judge) possui uma excelente seção de exercícios de SQL. A recomendação é seguir por grupos de dificuldade:
* **Grupo 1 (Projeção, Restrição)**: 2602, 2603, 2604, etc.
* **Grupo 2 (JOIN)**: 2605, 2606, 2611, etc.
* **Grupo 3 (GROUP BY, Subconsultas)**: 2609, 2993, 2994, etc.
* **Grupo 4 (Expressões)**: 2610, 2625, 2738, etc.
* **Grupo 5 (Diferença, União)**: 2616, 2737, 2740, etc.
* **Grupo 6 (Difíceis)**: 2988, 2989, 2991, etc.
### Estudo de Caso (Diagrama URI 2990)
Muitos exercícios utilizam um modelo de dados similar ao diagrama abaixo, que envolve empregados, departamentos, projetos e supervisão.
