📚 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-security no pom.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 do spring-boot-starter-security e recarregue o Maven (Ctrl+Shift+O no IntelliJ ou botão de reload). Sem ela, você acessa http://localhost:8080/h2-console normalmente. Garanta que o campo JDBC URL esteja como jdbc:h2:mem:teclojadb. Lá você verá os dados inseridos pelo DataSeeder.

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:

⚠️ 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.


Voltar para o Sumário