📚 Pré-requisitos Teóricos: Este projeto prático aplica conceitos de modelagem de relacionamentos objeto-relacionais (@ManyToOne), paginação eficiente com Spring Data JPA (Pageable), Bean Validation avançado e customização dinâmica com CSS Tokens ensinados na Especialização em Backend com Java e Spring Boot.

A Biblioteca de Jogos (v3) expande o modelo de dados para um ecossistema corporativo completo. A aplicação introduz o relacionamento relacional entre as entidades Jogo e Categoria, suporta paginação server-side com ordenação customizada, filtros dinâmicos por categoria, persistência de preferência visual através de Dark/Light Mode com CSS Custom Properties e validações semânticas de campos numéricos e textuais.


🏛️ Arquitetura de Paginação e Relacionamento JPA

O diagrama a seguir descreve o fluxo de dados entre os seletores da interface web, o controlador paginado e o motor de paginação SQL do Spring Data JPA:

Arquitetura de Paginação e Filtros na Biblioteca de Jogos

📁 Estrutura de Pastas Padronizada

A base de código segue o padrão canônico com nomenclatura anti-colisão:

catalogo_projetos/javaweb_jogos_03_paging/
├── assets/                   # Galeria de diagramas visuais e arquitetura
│   └── arquitetura_jogos_paging.svg
├── web-spring/               # Aplicação Spring Boot 3 Paginada
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/br/com/bibliotecajogos/
│   │   │   │   ├── controller/   # JogoController com rotas paginadas
│   │   │   │   ├── entity/       # Jogo & Categoria (@ManyToOne)
│   │   │   │   ├── repository/   # JogoRepository & CategoriaRepository
│   │   │   │   └── service/      # JogoService com Pageable
│   │   │   └── resources/
│   │   │       ├── templates/
│   │   │       │   └── index.html # Cards paginados e toggle de tema
│   │   │       ├── static/
│   │   │       │   └── css/style.css
│   │   │       └── application.properties
│   │   └── test/                 # Testes de paginação e validação
│   ├── Dockerfile
│   ├── pom.xml
│   ├── mvnw
│   └── mvnw.cmd
└── index.md                  # Especificação executiva e Padrão Ouro

🧑‍💼 Engenharia de Software & Requisitos

1. Histórias de Usuário (US)

ID Persona História de Usuário Prioridade
US-01 Gamer / Colecionador Desejo associar cada jogo a uma Categoria específica (RPG, Ação, Estratégia) para melhor catalogação. Alta
US-02 Usuário da Biblioteca Desejo paginar os resultados de 6 em 6 jogos para navegar com fluidez sem sobrecarregar a tela. Alta
US-03 Usuário da Biblioteca Desejo filtrar os jogos listados por categoria selecionada no cabeçalho da página. Alta
US-04 Gamer Noturno Desejo alternar entre temas claro e escuro mantendo minha preferência salva entre sessões no navegador. Média
US-05 Desenvolvedor Backend Desejo validar notas (0 a 10) e preços positivos diretamente no modelo antes da persistência. Alta

2. Requisitos Funcionais (RF)

ID Nome Descrição
RF-01 Associação de Categoria Relacionamento @ManyToOne entre Jogo e Categoria com chave estrangeira mapeada.
RF-02 Paginação com Spring Data Rota GET / recebendo parâmetros page, size e categoriaId, retornando Page<Jogo>.
RF-03 Filtragem Dinâmica Consulta JPA filtrando por identificador de categoria caso o parâmetro esteja presente.
RF-04 Bean Validation Numérico Validações @DecimalMin("0.0"), @DecimalMax("10.0") e @Positive na entidade Jogo.
RF-05 Tema Persistente Alternância de atributo data-theme gravado em localStorage para retenção no cliente.

3. Requisitos Não-Funcionais (RNF)

ID Nome Descrição
RNF-01 Eficiência de Consultas Execução com cláusulas SQL LIMIT e OFFSET geradas automaticamente pelo Spring Data.
RNF-02 Integridade Referencial Bloqueio de exclusão em cascata acidental garantindo integridade das categorias.
RNF-03 Tempo de Renderização Carga de página inferior a 90ms mesmo com catálogo de milhares de registros.
RNF-04 Acessibilidade Visual Contraste de cores mínimo de 4.5:1 nos cards e botões nos dois modos visuais.
RNF-05 Portabilidade em Nuvem Parâmetros de conexão JDBC configuráveis via variáveis de ambiente no Render.

4. Diagrama de Casos de Uso

flowchart TD
    subgraph Ator
        USR["Gamer / Usuário"]
    end

    subgraph "Biblioteca de Jogos v3 (web-spring)"
        UC1["Filtrar Jogos por Categoria"]
        UC2["Navegar entre Páginas (Pageable)"]
        UC3["Cadastrar Jogo com Categoria e Nota"]
        UC4["Alternar Modo Claro / Noturno"]
    end

    USR --> UC1
    USR --> UC2
    USR --> UC3
    USR --> UC4

5. Matriz de Rastreabilidade (RTM)

Requisito Funcional Histórias Atendidas Requisitos Não-Funcionais Status de Cobertura
RF-01 (Associação Categoria) US-01 RNF-01, RNF-02 Implementado ✅
RF-02 (Paginação Data JPA) US-02 RNF-01, RNF-03 Implementado ✅
RF-03 (Filtragem Dinâmica) US-03 RNF-01, RNF-03 Implementado ✅
RF-04 (Bean Validation) US-05 RNF-02 Implementado ✅
RF-05 (Tema Persistente) US-04 RNF-04 Implementado ✅

🛠️ Implementações Principais

1. Relacionamento na Entidade (Jogo.java)

📄 web-spring/src/main/java/br/com/bibliotecajogos/entity/Jogo.java (arquivo real do projeto)

package br.com.bibliotecajogos.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

import java.math.BigDecimal;
import java.time.LocalDate;

@Entity
public class Jogo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "O título é obrigatório")
    private String titulo;

    private String autor;
    private Integer anoPublicacao;
    private String genero;

    @Column(length = 1000)
    private String descricao;

    private String plataforma;
    private Integer tempoEstimado; // horas
    private LocalDate dataLancamento;

    @DecimalMin(value = "0.0", message = "A nota mínima é 0")
    @DecimalMax(value = "10.0", message = "A nota máxima é 10")
    private Double nota;

    @Enumerated(EnumType.STRING)
    private Dificuldade dificuldade;

    private boolean multiplayer;

    @Positive(message = "O preço deve ser positivo")
    private BigDecimal preco;

    private String lojaUrl;
    private String urlCapa;
    private boolean finalizado;

    private LocalDate dataAdicionado = LocalDate.now();
    private LocalDate dataFinalizacao;

    @NotNull(message = "A categoria é obrigatória")
    @ManyToOne
    private Categoria categoria;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitulo() { return titulo; }
    public void setTitulo(String titulo) { this.titulo = titulo; }
    public String getAutor() { return autor; }
    public void setAutor(String autor) { this.autor = autor; }
    public Integer getAnoPublicacao() { return anoPublicacao; }
    public void setAnoPublicacao(Integer anoPublicacao) { this.anoPublicacao = anoPublicacao; }
    public String getGenero() { return genero; }
    public void setGenero(String genero) { this.genero = genero; }
    public String getDescricao() { return descricao; }
    public void setDescricao(String descricao) { this.descricao = descricao; }
    public String getPlataforma() { return plataforma; }
    public void setPlataforma(String plataforma) { this.plataforma = plataforma; }
    public Integer getTempoEstimado() { return tempoEstimado; }
    public void setTempoEstimado(Integer tempoEstimado) { this.tempoEstimado = tempoEstimado; }
    public LocalDate getDataLancamento() { return dataLancamento; }
    public void setDataLancamento(LocalDate dataLancamento) { this.dataLancamento = dataLancamento; }
// … (+21 linhas: veja o arquivo completo no projeto)

2. Consulta Paginada no Controlador (JogoController.java)

📄 web-spring/src/main/java/br/com/bibliotecajogos/controller/JogoController.java (arquivo real do projeto)

package br.com.bibliotecajogos.controller;

import br.com.bibliotecajogos.entity.Categoria;
import br.com.bibliotecajogos.entity.Dificuldade;
import br.com.bibliotecajogos.entity.Jogo;
import br.com.bibliotecajogos.repository.CategoriaRepository;
import br.com.bibliotecajogos.service.JogoService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

@Controller
public class JogoController {

    @Autowired
    private JogoService jogoService;

    @Autowired
    private CategoriaRepository categoriaRepository;

    @GetMapping("/")
    public String listar(@RequestParam(defaultValue = "0") int pagina,
                          @RequestParam(required = false) Long categoriaId,
                          Model model) {
        var jogosPage = jogoService.listar(pagina, categoriaId);
        model.addAttribute("jogosPage", jogosPage);
        model.addAttribute("currentPage", pagina);
        model.addAttribute("categoriaId", categoriaId);
        model.addAttribute("categorias", categoriaRepository.findAll());
        model.addAttribute("novoJogo", new Jogo());
        model.addAttribute("dificuldades", Dificuldade.values());
        return "jogos";
    }

    @PostMapping("/jogos")
    public String salvar(@Valid @ModelAttribute("novoJogo") Jogo novoJogo, BindingResult result, Model model) {
        if (result.hasErrors()) {
            model.addAttribute("jogosPage", jogoService.listar(0, null));
            model.addAttribute("categorias", categoriaRepository.findAll());
            model.addAttribute("dificuldades", Dificuldade.values());
            return "jogos";
        }
        jogoService.salvar(novoJogo);
        return "redirect:/";
    }

    @PostMapping("/jogos/{id}/apagar")
    public String apagar(@PathVariable Long id) {
        jogoService.apagar(id);
        return "redirect:/";
    }
}

🚀 Roteiro de Execução

1. Iniciar a Aplicação com o Maven Wrapper

cd catalogo_projetos/javaweb_jogos_03_paging/web-spring
./mvnw spring-boot:run

No Windows (PowerShell):

cd catalogo_projetos\javaweb_jogos_03_paging\web-spring
.\mvnw.cmd spring-boot:run

2. Validar Recursos


🎯 Matriz de Checkpoints de Entrega

Checkpoint Critério de Aceite Validação Status
CP-01 Relacionamento @ManyToOne funcional com persistência relacional Chave estrangeira categoria_id populada PASS ✅
CP-02 Paginação com 6 itens por página controlada por Pageable Páginas divididas com ordenação alfabética PASS ✅
CP-03 Filtro seletivo por categoria retornando apenas registros associados Quantidade de registros ajustada dinamicamente PASS ✅
CP-04 Bean Validation bloqueando notas fora do intervalo [0.0, 10.0] Submissão inválida rejeitada com mensagem na tela PASS ✅
CP-05 Alternância de Dark Mode funcional e persistida no localStorage Token de tema recuperado automaticamente após reload PASS ✅