Programação para Web 3

Consulta de Dados

Prof. Me. Danilo Bueno

Relembrando

O que foi feito na aula anterior?

  • O banco de dados MySQL foi colocado no ar
  • As tabelas foram criadas por migrações do Flyway
  • Os dados iniciais foram inseridos

O banco está pronto, mas nenhuma linha de código Java o consulta.

De onde vêm os dados hoje?

O repositório ainda guarda os dados dentro de si:

private List<Produto> produtos = List.of(    new Produto("Espresso", 6.0));

Problemas com essa abordagem?

Consultando o banco

Como fazer o repositório buscar os dados no banco de dados?

Três passos principais:

  1. Conectar o repositório ao banco
  2. Executar a consulta SQL
  3. Converter o resultado em objetos

1. Conectando o repositório ao banco

Entre o código Java e o banco existem três camadas:

[ Código da aplicação ]
          ▼
[ JdbcTemplate ]
          ▼
[ JDBC ]
          ▼
[ Driver do MySQL ]
          ▼
[ Banco de dados ]

A camada JDBC

A API padrão do Java para bancos relacionais

  • Define interfaces como Connection, Statement e ResultSet
  • Não implementa nenhuma delas: estabelece apenas o contrato
  • Todo banco relacional é acessado por essas mesmas interfaces

É o que permite que o código Java não dependa de um banco específico.

A camada do driver

Quem de fato sabe conversar com o MySQL

  • Implementa as interfaces do JDBC para um banco específico
  • mysql-connector-j, adicionado na aula anterior, é o driver do MySQL
  • Traduz as chamadas Java para o protocolo daquele banco

O driver não aparece no nosso código: quem o utiliza, em tempo de execução, é o próprio JDBC.

A camada JdbcTemplate

Onde o Spring entra

  • Fica acima do JDBC e usa as mesmas interfaces por baixo
  • Cuida da conexão, da execução do comando e do fechamento dos recursos
  • Deixa para o desenvolvedor apenas o SQL e os parâmetros

É a única camada com a qual escrevemos código diretamente.

Uma consulta sem o JdbcTemplate

Usando apenas JDBC, uma consulta simples fica assim:

Connection conn = null;PreparedStatement stmt = null;ResultSet rs = null;try {    conn = dataSource.getConnection();    stmt = conn.prepareStatement("SELECT * FROM produtos");    rs = stmt.executeQuery();    while (rs.next()) { /* monta cada Produto */ }} catch (SQLException e) {    throw new RuntimeException(e);} finally {    rs.close(); stmt.close(); conn.close();}

O que o JdbcTemplate evita

A mesma consulta, agora com o JdbcTemplate:

String sql = "SELECT * FROM produtos";List<Map<String, Object>> linhas = jdbcTemplate.queryForList(sql);
  • Abertura e fechamento da conexão
  • Tratamento de SQLException em toda consulta
  • Percurso manual do ResultSet

O código repetitivo desaparece, e resta apenas o que muda de uma consulta para outra.

Obtendo o JdbcTemplate

Como o repositório recebe esse objeto?

@Repositorypublic class ProdutoRepository {    @Autowired    private JdbcTemplate jdbcTemplate;}

@Repository marca a classe como um repositório gerenciado pelo Spring.

O que o @Autowired evita

Sem ele, cada repositório teria de montar a própria conexão:

public ProdutoRepository() {    DriverManagerDataSource ds = new DriverManagerDataSource();    ds.setUrl("jdbc:mysql://localhost:3306/cafeteria");    ds.setUsername("root");    ds.setPassword("cafeteria");    this.jdbcTemplate = new JdbcTemplate(ds);}

A URL e a senha ficariam repetidas, em código, em cada classe que acessa o banco.

Injeção de dependência

O que acontece quando usamos @Autowired?

  • O Spring cria o JdbcTemplate uma vez, usando o application.properties
  • Ao criar o repositório, ele injeta esse objeto já pronto no campo
  • A classe declara o que precisa, em vez de construir

Esse mecanismo é chamado de injeção de dependência.

2. Executando a consulta SQL

Quais métodos o JdbcTemplate oferece para consultar?

Método Retorno Quando usar
queryForObject Um único valor Contagens, somas, um só campo
queryForList List<Map<String, Object>> Consultas com várias linhas

Consulta de um valor

O método queryForObject retorna um único resultado:

public int count() {    String sql = "SELECT COUNT(*) FROM produtos";    return jdbcTemplate.queryForObject(sql, Integer.class);}

Explicando o código

Método count

  • String sql: o comando SQL a ser executado no banco
  • queryForObject(): executa a consulta e retorna um único valor
  • Integer.class: informa o tipo Java esperado como retorno

Serve para consultas que sempre devolvem uma linha com uma coluna, como COUNT, SUM ou MAX.

Consulta de várias linhas

O método queryForList retorna todas as linhas encontradas:

String sql = "SELECT id, nome, preco FROM produtos";List<Map<String, Object>> linhas = jdbcTemplate.queryForList(sql);

Explicando o código

Método queryForList

  • Retorna uma lista de mapas: List<Map<String, Object>>
  • Cada Map da lista representa uma linha da tabela
  • As chaves do mapa são os nomes das colunas
{ "id" → 1, "nome" → "Espresso", "preco" → 6.00 }

E quando a consulta tem filtro?

Valores que só existem em tempo de execução usam o marcador ?:

String sql = "SELECT * FROM produtos WHERE preco <= ?";List<Map<String, Object>> linhas =    jdbcTemplate.queryForList(sql, precoMaximo);

Parâmetros posicionais

  1. Declaração na query: o ? é um espaço reservado para o valor real

  2. Vinculação do valor

    jdbcTemplate.queryForList(sql, precoMaximo);

    O valor vem após o SQL, na ordem em que os ? aparecem

Evita SQL Injection por não concatenar o valor diretamente na query, tratando-o sempre como dado.

3. Convertendo o resultado

Por que não devolver a lista de mapas direto?

  • O Map serve para qualquer consulta, mas não tem tipo definido
  • O restante da aplicação espera objetos Produto, com significado
  • É preciso converter cada Map em um objeto

A conversão fica concentrada em um único método, para não se repetir em cada consulta.

O método deMap

Recebe o mapa de uma linha e devolve um Produto:

public static Produto deMap(Map<String, Object> linha) {    Produto produto = new Produto();    produto.setId(((Number) linha.get("id")).longValue());    produto.setNome((String) linha.get("nome"));    produto.setPreco(((Number) linha.get("preco")).doubleValue());    return produto;}

Explicando o código

Método deMap

  • new Produto(): cria um objeto vazio, que será preenchido campo a campo
  • linha.get("nome"): busca o valor da coluna pelo nome
  • (String) e (Number): convertem o valor, que vem como Object

A classe Produto precisa ter um construtor sem argumentos e um set para cada atributo.

Por que converter para Number?

Colunas numéricas não chegam sempre no mesmo tipo Java:

  • Uma coluna DECIMAL é entregue como BigDecimal
  • Um cast direto para (Double) falha com ClassCastException
  • Number é o tipo comum a todos eles
produto.setPreco(((Number) linha.get("preco")).doubleValue());

Por que não converter direto?

produto.setId((long) linha.get("id"));  // não compilaproduto.setId((Long) linha.get("id"));  // compila, mas pode quebrar
  • Object não pode virar primitivo (long) direto por cast — só entre tipos referência
  • Cast pro wrapper exato (Long) só funciona se o valor for exatamente aquele tipo
  • Number é a superclasse comum: o cast sempre funciona, e .longValue() faz a conversão de verdade

Esse é o mesmo padrão usado ao ler JSON dinâmico ou qualquer API que devolve Object pra representar "um número, mas não sei qual subtipo": não é algo específico do JDBC.

Juntando tudo

Os três passos em um único método

public List<Produto> findAll() {    String sql = "SELECT id, nome, preco FROM produtos";    List<Map<String, Object>> linhas = jdbcTemplate.queryForList(sql);    List<Produto> produtos = new ArrayList<>();    for (Map<String, Object> linha : linhas) {        produtos.add(Produto.deMap(linha));    }    return produtos;}

Explicando o código

Método findAll

  • Executa a consulta e recebe a lista de mapas
  • Percorre cada linha do resultado com um laço
  • Converte cada mapa em um Produto e adiciona na lista
  • Devolve uma List<Produto> para quem chamou

O repositório completo

@Repositorypublic class ProdutoRepository {    @Autowired    private JdbcTemplate jdbcTemplate;    public List<Produto> findAll() { ... }    public int count() { ... }}

A classe não guarda nenhum dado: ela apenas sabe consultar o banco.

Usando o repositório no controller

@Controllerpublic class ProdutoController {    @Autowired    private ProdutoRepository produtoRepository;    @GetMapping("/produtos")    public String listar(Model model) {        List<Produto> produtos = produtoRepository.findAll();        model.addAttribute("produtos", produtos);        return "produtos";    }}

Explicando o código

Método listar

  • @Autowired: o Spring injeta o repositório, sem new
  • findAll(): busca os produtos no banco de dados
  • model.addAttribute(): envia a lista para a view
  • return "produtos": renderiza o template produtos.ftlh

Exibindo na view

O template percorre a lista recebida do controller:

<h1>Produtos</h1><ul><#list produtos as produto>    <li>${produto.nome}: R$ ${produto.preco?c}</li></#list></ul>

O caminho de uma requisição

[ Navegador ]        GET /produtos
       ▼
[ Controller ]       chama findAll()
       ▼
[ Repositório ]      queryForList + deMap
       ▼
[ JdbcTemplate ]     JDBC + driver
       ▼
[ Banco de dados ]   SELECT

A resposta percorre o mesmo caminho de volta, até virar HTML no navegador.

Obrigado!

Prof. Me. Danilo Bueno
[email protected]