Programação para Web 3

Prof. Me. Danilo Bueno

Relembrando

O que foi feito nas aulas anteriores?

  • A listagem passou a vir do banco de dados
  • O formulário de cadastro grava novos registros com INSERT
  • O repositório já sabe ler e escrever

A aplicação tem dados de verdade, mas um endereço só.

Como os produtos são exibidos?

Uma única rota devolve a lista inteira:

@GetMapping("/produtos")public String listar(Model model) {    model.addAttribute("produtos", produtoRepository.findAll());    return "produtos";}

Como abrir um produto específico?

Um endereço para cada produto

Como levar o usuário da lista até um item?

Quatro etapas:

  1. Dar um endereço próprio a cada recurso
  2. Ler o identificador que veio na URL
  3. Buscar um único registro no banco
  4. Tratar o recurso que não existe

1. Um endereço a cada recurso

O que é um recurso?

  • Cada produto do catálogo é uma coisa que a aplicação expõe
  • O que pode ser aberto, salvo nos favoritos ou enviado a outra pessoa precisa de endereço
  • Hoje existe o endereço da lista, mas não o de cada item

Na Web, aquilo que tem endereço próprio é chamado de recurso.

Duas formas de identificar

/produtos?id=7
/produtos/7

Analisando as duas formas:

Forma O 7 é Lido com
/produtos?id=7 um parâmetro da busca @RequestParam
/produtos/7 parte do caminho @PathVariable

De onde vêm as duas formas

Por que existem duas maneiras de dizer a mesma coisa?

/produto.php?id=7     o caminho nomeia um programa
/produtos/7           o caminho nomeia uma coisa
  • O caminho nasceu como endereço de um arquivo no disco
  • A query string nasceu para passar argumentos a um programa
  • O REST, proposto em 2000, popularizou a identificação pelo caminho

Nenhuma especificação da Web define que /produtos/7 é o produto 7: isso é convenção.

Por que o identificador vai no caminho

  • O caminho responde qual recurso, a query string responde como exibi-lo
  • /produtos/7 identifica o produto, ?ordem=preco ajustaria a visualização
  • Endereços assim são estáveis: podem ser salvos e compartilhados

A regra prática: o que identifica vai no caminho, o que ajusta a consulta vai na query string.

2. Lendo o identificador da URL

Uma rota com um trecho variável

@GetMapping("/produtos/{id}")
  • O trecho entre chaves é um espaço reservado
  • /produtos/7, /produtos/42 e /produtos/103 caem todos nessa rota
  • O valor que aparecer ali fica disponível para o método

Recebendo o valor no método

@GetMapping("/produtos/{id}")public String detalhar(@PathVariable Long id, Model model) {    Produto produto = produtoRepository.buscarPorId(id);    model.addAttribute("produto", produto);    return "produto";}

Explicando o código

Método detalhar

  • {id} na rota: marca a parte variável do caminho
  • @PathVariable Long id: recebe esse trecho e converte para Long
  • buscarPorId(id): pede ao repositório o registro correspondente
  • addAttribute("produto", ...): envia um objeto, não uma lista
  • return "produto": renderiza o template produto.ftlh

Analisando as duas anotações

@RequestParam @PathVariable
Lê de query string ou corpo caminho da URL
Exemplo /produtos?id=7 /produtos/7
Casa por nome do parâmetro nome entre chaves
Uso típico filtros e formulários identificação do recurso

E se o valor não for um número?

O endereço /produtos/abc chega à mesma rota:

  • O Spring tenta converter abc para Long antes de executar o método
  • A conversão falha, e a resposta é 400 Bad Request
  • O corpo do método não chega a rodar

O tipo declarado no @PathVariable funciona como primeira validação do endereço.

3. Buscando um registro único

A consulta agora precisa de filtro

SELECT * FROM produtos WHERE id = ?
  • O ? recebe o valor que veio da URL
  • O banco devolve, no máximo, uma linha
  • O valor continua separado do comando, como na listagem

O método buscarPorId

Consultando sem exigir que o registro exista

public Produto buscarPorId(Long id) {    String sql = "SELECT * FROM produtos WHERE id = ?";    List<Map<String, Object>> linhas =        jdbcTemplate.queryForList(sql, id);    if (linhas.isEmpty()) {        return null;    }    return Produto.deMap(linhas.get(0));}

Explicando o código

Método buscarPorId

  • queryForList: aceita zero, uma ou várias linhas sem reclamar
  • linhas.isEmpty(): separa o caso "não existe" do caso "existe"
  • return null: informa a ausência a quem chamou, sem lançar exceção
  • linhas.get(0): havendo linha, só pode haver uma, pois id é chave primária
  • deMap: a mesma conversão já usada na listagem

4. Tratando o recurso que não existe

O null chega ao controller

  • O repositório devolveu null: o endereço aponta para nada
  • Renderizar o template assim produziria uma página quebrada
  • Responder 200 diria ao navegador que a página existe

O código correto para "esse recurso não existe" é 404 Not Found.

Respondendo 404

@GetMapping("/produtos/{id}")public String detalhar(@PathVariable Long id, Model model) {    Produto produto = produtoRepository.buscarPorId(id);    if (produto == null) {        throw new ResponseStatusException(HttpStatus.NOT_FOUND);    }    model.addAttribute("produto", produto);    return "produto";}

Explicando o código

Uso do ResponseStatusException

  • É uma exceção do Spring que carrega um código de status
  • Ao ser lançada, interrompe o método e define a resposta
  • HttpStatus.NOT_FOUND corresponde ao código 404
  • Nenhum template é renderizado: o navegador recebe a página de erro

A verificação vem antes de qualquer uso do objeto, pelo mesmo motivo da validação no cadastro.

Exibindo os detalhes

O template produto.ftlh

<h1>${produto.nome}</h1><p>Preço: R$ ${produto.preco?c}</p><a href="/produtos">Voltar para a lista</a>

O template recebe um único objeto, por isso não há <#list>.

Ligando a listagem ao detalhe

Cada item da lista vira um link

<#list produtos as produto>    <li>        <a href="/produtos/${produto.id?c}">${produto.nome}</a>    </li></#list>

Explicando o código

Construção do endereço

  • O endereço é montado a partir do próprio dado, item por item
  • ${produto.id?c}: imprime o número sem separador de milhar
  • Cada linha da lista passa a apontar para o seu próprio recurso

Sem o ?c, o id 1998 sairia como 1,998, e o endereço não funcionaria.

O controller completo

@Controllerpublic class ProdutoController {    @Autowired    private ProdutoRepository produtoRepository;    @GetMapping("/produtos")    public String listar(Model model) { ... }    @GetMapping("/produtos/{id}")    public String detalhar(@PathVariable Long id, Model model) { ... }}

Duas rotas que começam igual, distinguidas pelo trecho final do caminho.

O caminho de uma requisição

[ Navegador ]      GET /produtos/7
       ▼
[ Controller ]     @PathVariable, id = 7
       ▼
[ Repositório ]    SELECT ... WHERE id = ?
       ▼
[ Banco ]          devolve zero ou uma linha
       ▼
[ Controller ]     404 se vazio, senão envia ao template
       ▼
[ View ]           produto.ftlh exibe os detalhes

Obrigado!

Prof. Me. Danilo Bueno
[email protected]