📚 Módulo 02: Backend - Setup Inicial e Repositórios
Com o banco de dados e as entidades mapeadas, daremos início à configuração física da nossa API e à implementação da Camada de Acesso a Dados (Repository). Ao final deste módulo, você terá a aplicação conectando-se localmente ao banco H2 e carregando dados reais de produtos de tecnologia automaticamente para os testes.
🛠️ 1. O Arquivo de Dependências (pom.xml)
O pom.xml configura as dependências do ecossistema Spring Boot no nosso backend. Note a configuração estrita do Java 17 e a inclusão das dependências de segurança e banco de dados.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>br.com.tecloja</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tecloja-api</name>
<description>API REST para E-commerce TecLoja</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Camada de Banco de Dados e ORM -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Banco em Memória (Desenvolvimento) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Driver PostgreSQL (Produção/Neon) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Camada Web e Validação -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Segurança e Autenticação JWT -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Utilitários de Desenvolvimento -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito para testes unitários -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- Projeto Lombok para produtividade -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.44</version>
<optional>true</optional>
</dependency>
<!-- Documentação e Testes Interativos via Swagger UI (OpenAPI 3) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.44</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
⚙️ 2. Configurações de Ambiente (Properties)
A divisão entre desenvolvimento local e deploy em produção na nuvem é feita por meio de Perfis do Spring.
💻 Desenvolvimento Local (src/main/resources/application.properties)
spring.application.name=tecloja-api
spring.profiles.active=dev
# Configuração do Banco de Dados H2 em Memória
spring.datasource.url=jdbc:h2:mem:teclojadb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# Habilita o Console H2 no navegador
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# ddl-auto=none: deixa o schema.sql ser a única fonte de verdade das tabelas
# Evita conflito de "table already exists" com create-drop + schema.sql simultâneos
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# Executa schema.sql na inicialização (cria todas as tabelas via SQL puro)
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql
# Chave secreta JWT para criptografia simétrica (HS256)
# ATENÇÃO: Em produção, use variável de ambiente ${JWT_SECRET_KEY}
tecloja.jwt.secret=9a4f2c8d3e6b1a7c5d9e8f2a1b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c
# Expiração do token (1 dia em milissegundos)
tecloja.jwt.expiration=86400000
☁️ Produção Cloud Neon (src/main/resources/application-prod.properties)
Utiliza variáveis de ambiente, que serão injetadas de forma segura na plataforma Render, sem expor credenciais no código-fonte do GitHub.
# Conexão segura com o Neon PostgreSQL
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.datasource.driverClassName=org.postgresql.Driver
# Dialeto PostgreSQL explícito (obrigatório para evitar inferência incorreta)
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# Em produção, nunca recriamos tabelas automaticamente
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
# Garante que o schema.sql de dev NÃO seja executado em produção
spring.sql.init.mode=never
# Pool de conexão otimizado para Neon (free tier)
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.connection-timeout=30000
# Chaves injetadas via variáveis de ambiente na plataforma Render
tecloja.jwt.secret=${JWT_SECRET_KEY}
tecloja.jwt.expiration=86400000
🗄️ 3. Interfaces de Repositório (Spring Data JPA)
Crie as interfaces na pasta br.com.tecloja.api.repository. A mágica do Spring Data JPA permite a geração automatizada de consultas SQL complexas apenas estendendo JpaRepository.
erDiagram
CATEGORIA ||--o{ PRODUTO : possui
PRODUTO ||--o{ ITEM_PEDIDO : pertence_a
PEDIDO ||--|{ ITEM_PEDIDO : contem
CLIENTE ||--o{ PEDIDO : realiza
USUARIO }o--o{ PAPEL : tem
1. CategoriaRepository.java
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Categoria;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CategoriaRepository extends JpaRepository<Categoria, Long> {
boolean existsByNomeIgnoreCase(String nome);
}
2. ProdutoRepository.java
Aqui ensinamos conceitos de Filtros e Consultas customizadas por nome e categoria.
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Produto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProdutoRepository extends JpaRepository<Produto, Long> {
// Utiliza Query Methods (Geração automática baseada em nomenclatura)
List<Produto> findByCategoriaId(Long categoriaId);
// Consulta customizada JPQL com busca parcial insensível a maiúsculas
@Query("SELECT p FROM Produto p WHERE LOWER(p.nome) LIKE LOWER(CONCAT('%', :busca, '%'))")
List<Produto> pesquisarPorNome(@Param("busca") String busca);
}
3. ClienteRepository.java
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Cliente;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ClienteRepository extends JpaRepository<Cliente, Long> {
Optional<Cliente> findByEmail(String email);
Optional<Cliente> findByCpf(String cpf);
}
4. PedidoRepository.java
Aqui ilustramos uma otimização de banco de dados essencial. O uso do JOIN FETCH impede o problema de performance N+1 ao carregar os itens e o cliente em uma única consulta SQL agrupada.
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Pedido;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
// Evita o problema N+1 carregando Pedido, Cliente e Itens em um único SELECT
@Query("SELECT DISTINCT p FROM Pedido p JOIN FETCH p.cliente JOIN FETCH p.itens ip JOIN FETCH ip.produto WHERE p.cliente.id = :clienteId")
List<Pedido> findPedidosCompletosPorCliente(@Param("clienteId") Long clienteId);
}
5. ItemPedidoRepository.java
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.ItemPedido;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ItemPedidoRepository extends JpaRepository<ItemPedido, Long> {}
6. PapelRepository.java
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Papel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface PapelRepository extends JpaRepository<Papel, Long> {
Optional<Papel> findByNome(String nome);
}
7. UsuarioRepository.java
package br.com.tecloja.api.repository;
import br.com.tecloja.api.model.Usuario;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
Optional<Usuario> findByUsername(String username);
}
🎯 4. Alimentação Automática do Banco (DataSeeder.java)
Para que as aulas práticas funcionem de imediato, criaremos o arquivo DataSeeder.java dentro de br.com.tecloja.api.config. Ela detectará se o banco está vazio e injetará categorias, produtos, perfis e credenciais de segurança básicas (senhas salvas via BCrypt).
package br.com.tecloja.api.config;
import br.com.tecloja.api.model.*;
import br.com.tecloja.api.repository.*;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Set;
@Component
public class DataSeeder implements CommandLineRunner {
private final CategoriaRepository categoriaRepository;
private final ProdutoRepository produtoRepository;
private final PapelRepository papelRepository;
private final UsuarioRepository usuarioRepository;
private final ClienteRepository clienteRepository;
public DataSeeder(CategoriaRepository categoriaRepository, ProdutoRepository produtoRepository,
PapelRepository papelRepository, UsuarioRepository usuarioRepository,
ClienteRepository clienteRepository) {
this.categoriaRepository = categoriaRepository;
this.produtoRepository = produtoRepository;
this.papelRepository = papelRepository;
this.usuarioRepository = usuarioRepository;
this.clienteRepository = clienteRepository;
}
@Override
public void run(String... args) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// 1. Criar Papéis de Segurança
if (papelRepository.count() == 0) {
papelRepository.save(new Papel(null, "ROLE_USER"));
papelRepository.save(new Papel(null, "ROLE_ADMIN"));
}
Papel papelUser = papelRepository.findByNome("ROLE_USER").orElse(null);
Papel papelAdmin = papelRepository.findByNome("ROLE_ADMIN").orElse(null);
// 2. Criar Usuários Administrativos / Clientes Didáticos
if (usuarioRepository.count() == 0) {
Usuario admin = new Usuario();
admin.setUsername("admin@tecloja.com");
admin.setSenha(encoder.encode("admin123"));
admin.setPapeis(Set.of(papelAdmin, papelUser));
usuarioRepository.save(admin);
Usuario user = new Usuario();
user.setUsername("usuario@email.com");
user.setSenha(encoder.encode("usuario123"));
user.setPapeis(Set.of(papelUser));
usuarioRepository.save(user);
}
if (clienteRepository.count() == 0) {
Cliente cliente = new Cliente();
cliente.setNome("Usuário Padrão");
cliente.setEmail("usuario@email.com");
cliente.setCpf("123.456.789-00");
clienteRepository.save(cliente);
}
// 3. Criar Categoria e Produtos de Tecnologia
if (categoriaRepository.count() == 0) {
Categoria smartphones = categoriaRepository.save(new Categoria(null, "Smartphones"));
Categoria notebooks = categoriaRepository.save(new Categoria(null, "Notebooks"));
Categoria acessorios = categoriaRepository.save(new Categoria(null, "Acessórios"));
// Alimentar Smartphones
Produto p1 = new Produto();
p1.setNome("iPhone 15 Pro Max");
p1.setDescricao("Processador A17 Pro, Câmera tripla de 48MP, 256GB");
p1.setPreco(new BigDecimal("9499.00"));
p1.setEstoque(15);
p1.setCategoria(smartphones);
produtoRepository.save(p1);
Produto p2 = new Produto();
p2.setNome("Samsung Galaxy S24 Ultra");
p2.setDescricao("Snapdragon 8 Gen 3, Caneta S-Pen, Câmera 200MP, 512GB");
p2.setPreco(new BigDecimal("7999.00"));
p2.setEstoque(20);
p2.setCategoria(smartphones);
produtoRepository.save(p2);
// Alimentar Notebooks
Produto p3 = new Produto();
p3.setNome("MacBook Air M2");
p3.setDescricao("Processador Apple M2, Tela Liquid Retina de 13.6, 8GB RAM, SSD 256GB");
p3.setPreco(new BigDecimal("8200.00"));
p3.setEstoque(8);
p3.setCategoria(notebooks);
produtoRepository.save(p3);
Produto p4 = new Produto();
p4.setNome("Notebook Gamer Dell G15");
p4.setDescricao("Intel Core i7, Nvidia RTX 4050, 16GB RAM, SSD 512GB");
p4.setPreco(new BigDecimal("5899.00"));
p4.setEstoque(10);
p4.setCategoria(notebooks);
produtoRepository.save(p4);
// Alimentar Acessórios
Produto p5 = new Produto();
p5.setNome("Fone Headphone Sony WH-1000XM5");
p5.setDescricao("Cancelamento ativo de ruído inteligente, Bateria de 30 horas");
p5.setPreco(new BigDecimal("2199.00"));
p5.setEstoque(30);
p5.setCategoria(acessorios);
produtoRepository.save(p5);
Produto p6 = new Produto();
p6.setNome("Teclado Mecânico Logitech MX Mechanical");
p6.setDescricao("Switches táteis silenciosos de perfil baixo, iluminação inteligente");
p6.setPreco(new BigDecimal("999.90"));
p6.setEstoque(25);
p6.setCategoria(acessorios);
produtoRepository.save(p6);
}
}
}
🏁 Conclusão e Próximos Passos
[!WARNING] Leia antes de iniciar o servidor — Erro 403 Forbidden no H2 Console Como já declaramos a dependência do
spring-boot-starter-securitynopom.xml, o Spring Boot ativou regras estritas de segurança (bloqueio de IFrames e proteção CSRF). O H2 Console usa essas tecnologias internamente, portanto ao acessá-lo agora você receberá 403 Forbidden (ou tela em branco).Solução temporária para explorar o banco neste módulo: No
pom.xml, comente a dependência dospring-boot-starter-securitye recarregue o Maven (Ctrl+Shift+Ono IntelliJ ou botão de reload). Sem ela, você acessahttp://localhost:8080/h2-consolenormalmente. Garanta que o campo JDBC URL esteja comojdbc:h2:mem:teclojadb. Lá você verá os dados inseridos peloDataSeeder.Reative a dependência ao terminar a exploração. Configuraremos a segurança corretamente no Módulo 05.
O backend está estruturado com a camada de persistência pronta! Inicie o servidor pela sua IDE ou via terminal:
./mvnw spring-boot:run
🔍 Checkpoint
Após iniciar a API, abra o console H2 em http://localhost:8080/h2-console (com security comentada) e execute:
SELECT COUNT(*) FROM PRODUTO;
SELECT USERNAME FROM USUARIO;
Resultado esperado:
COUNT(*)retorna6(seis produtos do seeder)USERNAMElistaadmin@tecloja.comeusuario@email.com
⚠️ Erros Comuns
| Sintoma | Causa | Solução |
|---|---|---|
Port 8080 already in use |
Outro processo usando a porta | Execute lsof -ti:8080 \| xargs kill (Linux/Mac) ou netstat -ano \| findstr :8080 + kill no Task Manager (Windows) |
H2 Console em branco ou 403 |
Spring Security bloqueia IFrames/CSRF | Comente spring-boot-starter-security no pom.xml temporariamente |
Table "PRODUTO" not found |
schema.sql não foi executado |
Confirme que spring.sql.init.mode=always está no application.properties |
DataSeeder não inseriu dados |
Banco não estava vazio no reinício | H2 é em memória — os dados do seeder são recriados a cada reinício, mas apenas se as tabelas estiverem vazias |
Cannot find symbol: classe Papel |
Compilou antes de criar os models | Execute ./mvnw clean compile para forçar recompilação de tudo |
No Módulo 03, criaremos os objetos de transferência de dados (DTOs), os conversores (Mappers) e programaremos uma infraestrutura centralizada para Tratamento Global de Erros, garantindo segurança e elegância no retorno de erros HTTP.