Programação para Web II

Fagno Alves Fonseca <fagno.fonseca@ifto.edu.br> Mestre em Modelagem Computacional de Sistemas – UFT.

1. Spring

O Spring é um framework open source para a plataforma Java. É composto de um conjunto de projetos independentes, da configuração à segurança, aplicativos da web a big data, quaisquer que sejam as necessidades de infraestrutura de seu aplicativo, há um Projeto Spring para ajudá-lo a construí-lo leia mais.

Principais projetos:

O Spring Framework: fornece um modelo abrangente de programação e configuração para aplicativos empresariais modernos baseados em Java, permite que o projeto possa rodar na web.

Spring Data: fornece um modelo de programação baseado em Spring familiar e consistente para acesso a dados, enquanto ainda retém as características especiais do armazenamento de dados subjacente.

Spring Security: é uma estrutura de autenticação e controle de acesso poderosa e altamente personalizável. É o padrão de fato para proteger aplicativos baseados em Spring. Concentra em fornecer autenticação e autorização para aplicativos Java.

Spring Boot: facilita a criação de aplicativos baseados em Spring autônomos e de nível de produção que você pode "simplesmente executar". Temos uma visão opinativa da plataforma Spring e das bibliotecas de terceiros para que você possa começar com o mínimo de confusão. A maioria dos aplicativos Spring Boot precisa de configuração mínima do Spring.

1.1. Spring MVC

O Spring MVC é um framework Java, moderno para desenvolvimento Web que usa recursos atuais da linguagem, com características para simplificar e fornecer maior produtividade ao desenvolvimento de aplicações web, utilizando o padrão arquitetural Model-View-Controller.

1.1.1. MVC

É um padrão criado com o objetivo de organizar a arquitetura de aplicações web em camadas com responsabilidades bem definidas.

MODEL: camada que representa as classes do domínio da aplicação, ou seja, a persistência de dados, validação e regras de negócio;

VIEW: camada que representa as views (interface) da nossa aplicação;

CONTROLLER: camada que controla a comunicação (request/response) entre as camadas Model e View.

1.2. Criando um Projeto Web com Spring

Temos algumas maneiras de configurar o Spring em nosso projeto web, seja por arquivos de configuração XML, ou via código Java, ou utilizando o Spring Boot.

Em nossas aulas iremos utilizar o Spring Boot para criar nossos projetos web utilizando o Spring.

Para criar nosso projeto com o Spring Boot, vamos utilizar a ferramenta web chamada Spring Initializr. Nesta ferramenta, definimos as dependências do nosso projeto e ela gera um arquivo com toda a estrutura básica de uma aplicação Spring, pronta para ser executada.

No Spring Quickstart Guide você pode ver como criar um exemplo prático de um projeto web com Spring.

1.2.1. Depedência

Ao criar um projeto com Sring Boot as dependências inseridas no projeto não possuem versão definida, ou seja, todas as demais dependências irão seguir a mesma referência definida na dependencia starter-parent conforme a seguir e disponível no seu arquivo pom.xml.

Dependências Spring Boot
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/>
    </parent>

Dependências terminadas com starter informa que essa dependência agrupa outras, tornando essa dependência única, pois a mesma, representa todas as demais dependências necessária ao uso do recurso. Como beneficio, torna o pom.xml menor.

A seguir temos a dependência necessária para que possamos criar um projeto Spring Web (Build web, including RESTful, applications using Spring MVC). Esta dependência deve ser adicionada ao criar o projeto no Spring Boot.

Dependências Spring Boot
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
O Spring adiciona ao projeto o Apache Tomcat como contêiner padrão.

1.3. Definindo um Controller

Controller são classes contendo a lógica de negócio do seu sistema. É responsável por receber todas as requisições do usuário. Uma camada intermediária entre a camada de apresentação e a lógica.

Antes de criar nosso controller, vamos definir uma entidade para representar uma pessoa.

Entidade Pessoa
public class Pessoa{

    private Long id;
    private String nome;

    //getters e setters

}

Após definir a classe Pessoa, vamos criar uma nova classe para representar o Controller e tratar as requisições web. Essa classe vai ser o Controller de pessoas.

A nova classe deve ser definida com o nome PessoasController. Para indicar ao Spring MVC que uma classe é um Controller, adicione sobre ela a anotação @Controller conforme o exemplo a seguir.

Criando um Controller
import org.springframework.stereotype.Controller;

@Controller
public class PessoasController{
    ...
}

Anotando seu controller com @Controller, todos os seus métodos públicos serão acessíveis pela web. No exemplo a seguir, uma requisição à URI "http://localhost:8080/pessoas/exemplo" redirecionará para o método exemplo().

PessoasController
@Controller
@RequestMapping("pessoas")
public class PessoasController {

    @ResponseBody
    @GetMapping("/exemplo")
    public String exemplo(){
        return "Controller de Pessoas!";
    }

}

Com a anotação @RequestMapping("pessoas"), caso uma requisição seja enviada para localhost:8080/pessoas, essa requisição será tratada por um dos métodos a serem implementados neste Controller.

A anotação @ResponseBody diz ao Spring MVC para jogar o retorno do método na resposta.

A anotação @GetMapping indica ao Spring MVC que requisições HTTP do tipo GET enviadas para localhost:8080/pessoas/exemplo devem ser atendidas pelo método exemplo().

1.4. Definindo PessoaDAO

No exemplo a seguir, utilizamos um DAO para operações de CRUD aos dados das pessoas no banco. Iremos utilizar uma conexão JDBC, pois nosso objetivo nete capítulo é conhecer as funcionalidades do Spring.

PessoaDAO
public class PessoaDAO {

    //criar um objeto Connection para receber a conexão
    Connection con;

    public PessoaDAO(){
        con = MinhaConexao.conexao();
    }

    public List<Pessoa> buscarPessoas() {
        try {
            //comando sql
            String sql = "select * from tb_pessoa";
            PreparedStatement ps = con.prepareStatement(sql);
            //ResultSet, representa o resultado do comando SQL
            ResultSet rs = ps.executeQuery();
            //cria uma lista de pessoas para retornar
            List<Pessoa> pessoas = new ArrayList();
            //laço para buscar todas as pessoas do banco
            while (rs.next()) {
                Pessoa p = new Pessoa();
                p.setId(rs.getInt("id"));
                p.setNome(rs.getString("nome"));
                //add pessoa na lista
                pessoas.add(p);
            }
            //retorna a lista de pessoas
            return pessoas;
        } catch (SQLException ex) {
            Logger.getLogger(PessoaDAO.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    public boolean remove(Long id) {
        try {
            //comando sql
            String sql = "delete from tb_pessoa where id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            //referênciar o parâmetro do método para a ?
            ps.setLong(1, id);
            if(ps.executeUpdate()==1)
                return true;

        } catch (SQLException ex) {
            Logger.getLogger(PessoaDAO.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;
    }

    public boolean save(Pessoa pessoa) {
        try {
            //comando sql
            String sql = "insert into tb_pessoa (nome) values (?)";
            PreparedStatement ps = con.prepareStatement(sql);
            //referênciar o parâmetro do método para a ?
            ps.setString(1, pessoa.getNome());

            if(ps.executeUpdate()==1)
                return true;

        } catch (SQLException ex) {
            Logger.getLogger(PessoaDAO.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;
    }

    public boolean update(Pessoa pessoa) {
        try {
            //comando sql
            String sql = "update tb_pessoa set nome=? where id=?";
            PreparedStatement ps = con.prepareStatement(sql);
            //referênciar o parâmetro do método para a ?
            ps.setString(1, pessoa.getNome());
            ps.setLong(2, pessoa.getId());

            if (ps.executeUpdate()==1)
                return true;

        } catch (SQLException ex) {
            Logger.getLogger(PessoaDAO.class.getName()).log(Level.SEVERE, null, ex);
        }
        return false;
    }

    public Pessoa buscarPessoa(Long id) {
        try {
            //comando sql
            String sql = "select * from tb_pessoa where id = ?";
            PreparedStatement ps = con.prepareStatement(sql);
            //referênciar o parâmetro do método para a ?
            ps.setLong(1, id);
            //ResultSet, representa o resultado do comando SQL
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                Pessoa p = new Pessoa();
                p.setId(rs.getInt("id"));
                p.setNome(rs.getString("nome"));
                return p;
            }
        } catch (SQLException ex) {
            Logger.getLogger(PessoaDAO.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

}

Para efetuar a conexão com o banco de dados, vamos utilizar a interface e classes definidas a seguir.

Interface ConexaoJDBC.java
...
import java.sql.Connection;

public interface ConexaoJDBC{

    public Connection criarConexao();

}

A seguir temos as classes para conexão com banco Mysql e Postgres. Utilize a classe do banco de dados de sua escolha.

Classe ConexaoMysql.java
...
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ConexaoMysql implements ConexaoJDBC{

    public static void main(String[] args) {

        //testar conexão
        System.out.println(new ConexaoMysql().criarConexao());

    }

    /**
     * método que vai retornar uma conexão
     * @return
     */
    @Override
    public Connection criarConexao(){
        try {
            //carregar o driver de conexão
            Class.forName("com.mysql.cj.jdbc.Driver");
            //parâmetros
            String url = "jdbc:mysql://localhost:3306/teste";
            String usuario = "root";
            String senha = "senha";
            //retorna a conexão com o banco de dados
            return DriverManager.getConnection(url, usuario, senha);

        } catch (ClassNotFoundException | SQLException ex) {
            Logger.getLogger(ConexaoMysql.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}
Classe ConexaoPostgre
...
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ConexaoPostgre implements ConexaoJDBC{

    public static void main(String[] args) {

        //testar conexão
        System.out.println(new ConexaoPostgre().criarConexao());

    }

    /**
     * método que vai retornar uma conexão
     * @return
     */
    @Override
    public Connection criarConexao(){
        try {
            //carregar o driver de conexão
            Class.forName("org.postgresql.Driver");
            //parâmetros
            String url = "jdbc:postgresql://localhost:5432/teste";
            String usuario = "postgres";
            String senha = "senha";
            //retorna a conexão com o banco de dados
            return DriverManager.getConnection(url, usuario, senha);

        } catch (ClassNotFoundException | SQLException ex) {
            Logger.getLogger(ConexaoPostgre.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }


}
Classe que define sua conexão com o banco de dados.
...
import java.sql.Connection;

public class MinhaConexao{

    public static Connection conexao(){
        ConexaoJDBC conexao = new ConexaoMysql();
        return conexao.criarConexao();
    }

}

1.4.1. Banco de dados em memória

O H2 é um sistema de gerenciamento de banco de dados relacional escrito em Java.

O H2 possui suporte a bancos de dados persistentes e na memória, não tendo limite para o número de bancos de dados abertos simultaneamente ou para o número de conexões abertas.

Para criar um banco de dados em mémoria, adicione as propriedades a seguir no seu arquivo application.properties.

application.properties
##H2database
spring.datasource.url=jdbc:h2:mem:dbname
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=user
spring.datasource.password=password

Por fim, incluir no diretório resources do seu projeto o arquivo data.sql. Neste arquivo, você deve incluir comandos para criar tabelas e fazer inserções de dados.

Quando rodarmos o projeto com este arquivo no classpath, o Spring irá usá-lo para preencher o banco de dados.

Você pode habilitar o console GUI (interface gráfica do utilizador) embutido para navegar pelo conteúdo do banco de dados e executar consultas SQL.

Para acessar o console utilize o endereço http://localhost:8080/h2-console.

application.properties
#h2 console
spring.h2.console.enabled=true

Necessário incluir a depência do JPA a seguir.

Dependência
<!-- Persist data in SQL stores with Java Persistence API using Spring Data and Hibernate.-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

1.5. Implementando ações no controller

Com nossa classe PessoaDAO pronta podemos definir as operações necessárias no controlador com todas as operações de CRUD para Pessoa.

Conexao.java
@Controller
@RequestMapping("pessoas")
public class PessoasController {

    PessoaDAO dao;

    public PessoasController(){
        dao = new PessoaDAO();
    }

    /**
     * @param pessoa necessário devido utilizar no form.html o th:object que faz referência ao objeto esperado no controller.
     * @return
     */
    @GetMapping("/form")
    public String form(Pessoa pessoa){
        return "/pessoas/form";
    }

    @GetMapping("/list")
    public ModelAndView listar(ModelMap model) {
        model.addAttribute("pessoas", dao.buscarPessoas());
        return new ModelAndView("/pessoas/list", model);
    }

    @PostMapping("/save")
    public ModelAndView save(Pessoa pessoa){
        dao.save(pessoa);
        return new ModelAndView("redirect:/pessoas/list");
    }

    /**
     * @param id
     * @return
     * @PathVariable é utilizado quando o valor da variável é passada diretamente na URL
     */
    @GetMapping("/remove/{id}")
    public ModelAndView remove(@PathVariable("id") Long id){
        dao.remove(id);
        return new ModelAndView("redirect:/pessoas/list");
    }

    /**
     * @param id
     * @return
     * @PathVariable é utilizado quando o valor da variável é passada diretamente na URL
     */
    @GetMapping("/edit/{id}")
    public ModelAndView edit(@PathVariable("id") Long id, ModelMap model) {
        model.addAttribute("pessoa", dao.buscarPessoa(id));
        return new ModelAndView("/pessoas/form", model);
    }

    @PostMapping("/update")
    public ModelAndView update(Pessoa pessoa) {
        dao.update(pessoa);
        return new ModelAndView("redirect:/pessoas/list");
    }

}

Com o método form(), atendemos requisições do tipo GET enviadas para /pessoas/form, que tambem carrega a página form.html através do returno do método. Declaramos um parâmetro do tipo Pessoa, necessário devido utilizar no form.html o th:object que faz referência ao objeto esperado no controller.

Para buscar as pessoas do banco de dados, temos uma intância de PessoaDAO. Através desta instância temos acesso ao método buscarPessoas(), que retorna a lista de pessoas do banco.

O @param model representa a implementação de um map feita pelo Spring. O Spring nos fornecerá um objeto desse tipo já instanciado. Através deste objeto, temos acesso ao método addAttribute(), que usamos para enviar dados para a view, adicionando no map o atributo que desejamos enviar.

No return de alguns métodos, retornamos um objeto do tipo ModelAndView. Na instanciação desse objeto passamos por parâmetros o model no qual acabamos de adicionar e uma string, que indica a página a ser exibida. Com o retorno do ModelAndView, o Controller enviar as informações para a view, que carrega a página list.html com os dados enviados pelo model, atendendo requisições do tipo GET enviadas para /pessoas/list.

caminho view spring
Figura 1. caminho da view no diretório do projeto

Observe, que nossa view foi definida em src/main/resources dentro do pacote templates. Foi definido um novo pacote pessoas para representar as view de Pessoas.

Para visualizar a lista de pessoas através da requisição à URI "http://localhost:8080/pessoas/list" você deve definiar seu arquivo lista.html conforme a seguir.

list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <table class="table" border="1">
            <thead>
                <tr>
                    <th>Id</th>
                    <th>Descrição</th>
                </tr>
            </thead>
            <tr th:each="p : ${pessoas}">
                <td th:text="${p.id}"> </td>
                <td th:text="${p.nome}"> </td>
                <td> <a th:href="@{/pessoas/edit/{id}(id=${p.id})}">Editar</a> </td>
                <td> <a onclick="return window.confirm('Tem certeza que deseja excluir este registro?')"  th:href="@{/pessoas/remove/{id}(id=${p.id})}">Excluir</a> </td>
            </tr>
        </table>

    </body>
</html>

Na tag raiz do html adicionamos o namespace do Thymeleaf xmlns:th="http://www.thymeleaf.org", para que possamos ter acesso aos componentes do Thymeleaf.

Para utilizar o Thymeleaf no seu projeto é necessário adicionar a dependência do mesmo, então basta incluir no seu arquivo pom.xml a depência a seguir, caso ainda não tenha feito.

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

A instruçẽo th:each define um foreach a partir da lista de pessoas enviada pelo nosso controller. Já a th:text exibe os dados obtidos por cada item da lista.

Para atualizar os dados de uma pessoa, utilizamos o mesmo form.html, através do método edit() carregamos os dados do objeto a ser editado na view. E utilizando o método update() para efetuar as alterações no banco. Observe no código a seguir como definir no form.html.

form.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>

        <h1>FORM</h1>
        <form th:action="${pessoa.id == null} ? @{/pessoas/save} : @{/pessoas/update}" th:object="${pessoa}" method="post">
            <input type="hidden" th:field="*{id}">
            <input type="text" th:field="*{nome}"  placeholder="Nome">
            <input type="submit" value="Enviar">
        </form>

    </body>
</html>

Observe que temos uma condicional que verifica se o id do objeto Pessoa é null. Desta forma, identificamos qual ação o th:action deve executar no submit.

2. Referências