Incrementando aplicações Spring Boot

Código fonte disponível em: https://github.com/waltercoan/ProjetoSpring

Acesse a aplicação em execução: https://springboot-waltercoan.herokuapp.com

O objeto deste material é apresentar componentes avançados para o desenvolvimento de aplicações complexas utilizando o Spring Boot, segue abaixo a lista de componentes:

Campo de data com componente de calendário

Um tipo de dado muito comum em qualquer aplicação são campos para armazenar Data e Hora, e normalmente na interface gráfica são utilizados componentes gráficos que simplificam o processo de digitação por parte do usuário. O objetivo dessa sessão é apresentar como construir esse tipo de campo.
Primeiramente precisamos verificar nosso objeto MODEL Paciente, alteramos esta classe para inclusão de um novo atributo chamado dataNascimento utilizando como tipo a classe java.util.Date:

  • Na linhas 25 – inserimos a anotação @Temporal(value=TemporalType.TIMESTAMP), que indica para o JPA/Hibernate que no banco de dados deverá ser criado um campo do tipo timestamp
  • Na linha 26 – inserimos a anotação @DateTimeFormat(pattern = “dd/MM/yyyy H:mm”) para indicar ao Spring qual o formato do dado que será recebido do campo na tela, para que ele faça a conversão do valor em String automaticamente para o objeto java.util.Date
  • Nas linhas 29 à 34 inserimos os métodos GET/SET necessários para que o JPA/Hibernate insira esse atributo no banco de dados
package br.com.faltoupontoevirgula.projetospring.model;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Paciente {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    @NotNull
    @Length(min=2, max=1000, message="O tamanho do nome deve ser entre {min} e {max}")
    private String nome;
    private String sexo;
    @Temporal(value=TemporalType.TIMESTAMP)
    @DateTimeFormat(pattern = "dd/MM/yyyy H:mm")
    private Date dataNascimento;
   
    public Date getDataNascimento() {
        return dataNascimento;
    }
    public void setDataNascimento(Date dataNascimento) {
        this.dataNascimento = dataNascimento;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }
    public String getSexo() {
        return sexo;
    }
    public void setSexo(String sexo) {
        this.sexo = sexo;
    }
}

Agora antes de inserir esse novo campo na tela, vamos incluir algumas dependências no nosso arquivo pom.xml, para que a biblioteca JavaScript Tempus Dominus, que cria o componente na interface gráfica no formato de calendário possa ser utilizada. Maiores informações sobre esta biblioteca pode ser encontrada no site: https://tempusdominus.github.io/bootstrap-4/Usage/

Conforme a figura a seguir, clique duas vezes sobre o arquivo pom.xml -> selecione a aba Dependencies e clique no botão ADD.

Agora precisamos incluir três novas dependências, portanto repita o processo para cada um das libs abaixo:

Group Id: org.webjars.bower
Artifact Id: moment
Version: 2.22.2

Group Id: org.webjars.bower
Artifact Id: tempusdominus-bootstrap-4
Version: 5.0.0-alpha.16

Group Id: org.webjars
Artifact Id: font-awesome
Version: 4.7.0

Após concluir a inclusão desta dependências, salve o arquivo pom.xml para que o Maven fazer o download e inclusão das dependências no projeto.

As três dependências que foram inseridas no arquivo pom.xml, são bibliotecas JavaScript necessárias para o funcionamento do componente de calendário na tela, portanto precisamos carregar seu código JavaScript e CSS. Para isso altere o código do arquivo resources/templates/layout.html para inserir as seguintes linhas conforme exemplo abaixo:

  • Na linha 5 inserimos a tag link para carregar o arquivo CSS tempusdominus-bootstrap-4.min.css
  • Na linha 6 inserimos a tag link para carregar o arquivo CSS font-awesome.min.css
  • Na linha 26 inserimos a tag script para carregar o arquivo JS moment.min.js
  • Na linha 27 inserimos a tag script para carregar o arquivo JS tempusdominus-bootstrap-4.min.js
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:fragment="htmlhead">
        <link rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.min.css}" />
        <link rel="stylesheet" th:href="@{/webjars/tempusdominus-bootstrap-4/5.0.0-alpha.16/build/css/tempusdominus-bootstrap-4.min.css}" />
        <link rel="stylesheet" th:href="@{/webjars/font-awesome/4.7.0/css/font-awesome.min.css}" />
    </head>
    <body>
        <nav th:fragment="menu" class="navbar navbar-expand-lg navbar-dark bg-dark">
            <a class="navbar-brand" href="#">Sistema</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
                <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                  <li class="nav-item active">
                    <a class="nav-link" href="#">Home</a>
                  </li>
                </ul>
            </div>
        </nav>
       
        <div th:fragment="footer" id="footer">
            <script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"></script>
            <script th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.min.js}"></script>
            <script th:src="@{/webjars/moment/2.22.2/min/moment.min.js}"></script>
            <script th:src="@{/webjars/tempusdominus-bootstrap-4/5.0.0-alpha.16/build/js/tempusdominus-bootstrap-4.min.js}"></script>
        </div>
    </body>
</html>

Agora que as bibliotecas foram importadas para o layout, podemos alterar nosso formulário do paciente (resources/templates/paciente/form.html) para incluir o campo data de nascimento, conforme o código de exemplo abaixo:

  • Nas linhas 23 à 32 inserimos campo de data com calendário, esse conjunto de tags foi baseado no manual da biblioteca Tempus Dominus conforme o link:https://tempusdominus.github.io/bootstrap-4/Usage/ IMPORTANTE destacar que a tag INPUT recebeu a propriedade th:field=”*{dataNascimento}” que realiza a conexão automática deste campo com o atributo dataNascimento do objeto model.
  • Nas linhas 39 à 45, inserimos a tag Script com o código jQuery necessário para carregar o componente de calendário no campo datetimepicker1. IMPORTANTE: este tag script DEVE ficar depois da tag que insere o footer do layout, pois nessa etapa que são carregadas as dependências do jQuery + Bootstrap + Tempus Dominus
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <nav th:replace="layout :: menu">(menu)</nav>
            <div class="border border-dark">
                <form th:action="@{/paciente/(form)}" th:object="${paciente}" action="#" method="post">
                    <input type="hidden" id="txtid" th:field="*{id}" />
                    <div class="form-group">
                        <label for="txtnome">Nome</label>
                        <input type="text" th:field="*{nome}" class="form-control" id="txtnome" placeholder="Nome">
                    </div>
                    <div class="form-group">
                        <label for="slcsexo">Sexo</label>
                        <select class="form-control" id="slcsexo" th:field="*{sexo}">
                            <option th:value="Masculino">Masculino</option>
                            <option th:value="Feminino">Feminino</option>
                            <option th:value="Outro">Outro</option>
                        </select>
                    </div>
                   
                    <div class="form-group">
                        <label for="slcsexo">Data Nascimento</label>
                        <div class="input-group date" id="datetimepicker1" data-target-input="nearest">
                            <input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1"
                                th:field="*{dataNascimento}"/>
                            <div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
                                <div class="input-group-text"><i class="fa fa-calendar"></i></div>
                            </div>
                        </div>
                    </div>
                     
                    <button type="submit" class="btn btn-primary">Enviar</button>
                </form>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
        <script type="text/javascript">
            $(function () {
                $('#datetimepicker1').datetimepicker({
                    format:"DD/MM/YYYY HH:mm"                  
                });
            });
        </script>
    </body>
</html>

Para concluir, precisamos alterar a listagem de pacientes, no arquivo (resources/templates/paciente/index.html) para incluir o campo de data de nascimento na tela principal (isso não é obrigatório, depende apenas da necessidade da sua aplicação).

  • Na linha 14 inserimos uma nova coluna chamada Data Nascimento
  • Na linha 22 inserimos efetivamente o campo da data de nascimento e utilizamos o método format para apresentar a data no formato do Brasil
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <nav th:replace="layout :: menu">(menu)</nav>
            <a href="form.html" th:href="@{/paciente/novo}" class="btn btn-primary btn-lg" role="button">Novo</a>
            <div class="border border-dark">
                <table class="table">
                    <thead class="thead-light">
                        <tr>
                            <th scope="col">Nome</th>
                            <th scope="col">Sexo</th>
                            <th scope="col">Data Nascimento</th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr scope="row" th:each="umpac : ${listapac}">
                            <td th:text="${umpac.nome}"></td>
                            <td th:text="${umpac.sexo}"></td>
                            <td th:text="${#dates.format(umpac.dataNascimento, 'dd/MM/yyyy HH:mm')}"></td>
                            <td>
                                <a href="form.html" th:href="@{'/paciente/alterar/' + ${umpac.id}}" class="btn btn-secondary btn-lg" role="button">Alterar</a>
                                <a href="#" class="btn btn-warning btn-lg" role="button" data-toggle="modal" data-target="#confirmaExclusao" th:data-id="${umpac.id}" rel="noopener noreferrer">Excluir</a>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div class="modal fade" id="confirmaExclusao" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">Confirmação de exclusão</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">X</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <p>Confirma a exclusão deste registro do Paciente?</p>
                        </div>
                        <div class="modal-footer">
                            <a href="#" class="btn btn-warning btn-lg" id="btnConf" role="button">Sim</a>
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
        <script th:inline="javascript">
          /*<![CDATA[*/
               $('#confirmaExclusao').on('show.bs.modal', function (event) {
                  var button = $(event.relatedTarget)
                  var recipient = button.data('id')
                  var modal = $(this)
                  modal.find('#btnConf').attr("href","/paciente/remover/" + recipient)
                })
           /*]]>*/
        </script>
    </body>
</html>

Campo com formato CPF ou telefone

Campos com formato são utilizados em tipos de dados com alguma organização estrutural como CPF ou telefone. Para exemplificar este uso vamos inserir o campo CPF na classe Paciente, e na interface gráfica utilizar a biblioteca jQuery Mask Bootstrap para realizar a formatação do campo. Mais informações sobre o funcionamento da biblioteca pode ser encontrado no link: https://github.com/noweb/jQuery-Mask-Bootstrap

Para iniciar, vamos primeiro alterar nossa classe model /model/Paciente.java para incluir o campo CPF do tipo String, desta forma tanto os dados como o formato serão armazenados no banco de dados. Para isso, conforme o código abaixo, vamos inserir na linha 28 o atributo CPF e nas linhas 55 à 60 criar os métodos GET/SET.

package br.com.faltoupontoevirgula.projetospring.model;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Paciente {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    @NotNull
    @Length(min=2, max=1000, message="O tamanho do nome deve ser entre {min} e {max}")
    private String nome;
    private String sexo;
    @Temporal(value=TemporalType.TIMESTAMP)
    @DateTimeFormat(pattern = "dd/MM/yyyy H:mm")
    private Date dataNascimento;
    private String CPF;
   
   
    public Date getDataNascimento() {
        return dataNascimento;
    }
    public void setDataNascimento(Date dataNascimento) {
        this.dataNascimento = dataNascimento;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }
    public String getSexo() {
        return sexo;
    }
    public void setSexo(String sexo) {
        this.sexo = sexo;
    }
    public String getCPF() {
        return CPF;
    }
    public void setCPF(String cPF) {
        CPF = cPF;
    }
}

Agora precisamos alterar nosso arquivo pom.xml para incluir a dependência da biblioteca jQuery Mask Bootstrap, para isso clique duas vezes sobre o arquivo pom.xml, abra a aba dependencies e selecione a opção ADD.

Na próxima tela inclua a biblioteca do jQuery Mask Bootstrap, conforme imagem abaixo:

Group Id: org.webjars
Artifact Id: jquery-maskedinput
Version: 1.3.1

Para concluir, salve o arquivo pom.xml para que o Maven faça o download e a inclusão da dependência.

Uma vez que a dependência biblioteca jQuery Mask Bootstrap foi incluída no projeto, precisamos alterar nosso arquivo de layout (/resources/templates/layout.html) para importar o código JavaScript da biblioteca para todas as páginas.

  • Na linha 28 inserimos o tag script para importar o código da biblioteca jquery.maskedinput.min.js
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:fragment="htmlhead">
        <link rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.min.css}" />
        <link rel="stylesheet" th:href="@{/webjars/tempusdominus-bootstrap-4/5.0.0-alpha.16/build/css/tempusdominus-bootstrap-4.min.css}" />
        <link rel="stylesheet" th:href="@{/webjars/font-awesome/4.7.0/css/font-awesome.min.css}" />
    </head>
    <body>
        <nav th:fragment="menu" class="navbar navbar-expand-lg navbar-dark bg-dark">
            <a class="navbar-brand" href="#">Sistema</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
                <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                  <li class="nav-item active">
                    <a class="nav-link" href="#">Home</a>
                  </li>
                </ul>
            </div>
        </nav>
       
        <div th:fragment="footer" id="footer">
            <script th:src="@{/webjars/jquery/3.0.0/jquery.min.js}"></script>
            <script th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.min.js}"></script>
            <script th:src="@{/webjars/moment/2.22.2/min/moment.min.js}"></script>
            <script th:src="@{/webjars/tempusdominus-bootstrap-4/5.0.0-alpha.16/build/js/tempusdominus-bootstrap-4.min.js}"></script>
            <script th:src="@{/webjars/jquery-maskedinput/1.3.1/jquery.maskedinput.min.js}"></script>
        </div>
    </body>
</html>

Agora podemos alterar o formulário (/resources/templates/paciente/form.html) para incluir o novo campo CPF, e habilitar o JavaScript através da função jQuery para incluir a máscara conforme o formato definido.

  • Nas linhas 34 à 37 – inserimos o conjunto de tags HTML LABEL e INPUT para inserir o campo de texto comum, e utilizamos a propriedade th:field=”*{CPF}” para realizar o binding com a propriedade CPF da classe Paciente
  • Nas linhas 53 à 57 – inserimos um tag Script que possui uma função seletora do jQuery para modificar o tag HTML cujo id é txtCPF, chamado a função mask e passando o formato “999.999.999-99”. IMPORTANTE: este tag SCRIPT deve ficar depois da diretiva que inclui o footer do layout, pois nesse trecho de código que a biblioteca jQuery Masked Input é carregada para o HTML. Outro ponto importante é que não é necessário existir um tag Script para cada código JavaScript, o código foi separado apenas para melhorar a visualização.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <nav th:replace="layout :: menu">(menu)</nav>
            <div class="border border-dark">
                <form th:action="@{/paciente/(form)}" th:object="${paciente}" action="#" method="post">
                    <input type="hidden" id="txtid" th:field="*{id}" />
                    <div class="form-group">
                        <label for="txtnome">Nome</label>
                        <input type="text" th:field="*{nome}" class="form-control" id="txtnome" placeholder="Nome">
                    </div>
                    <div class="form-group">
                        <label for="slcsexo">Sexo</label>
                        <select class="form-control" id="slcsexo" th:field="*{sexo}">
                            <option th:value="Masculino">Masculino</option>
                            <option th:value="Feminino">Feminino</option>
                            <option th:value="Outro">Outro</option>
                        </select>
                    </div>
                   
                    <div class="form-group">
                        <label for="slcsexo">Data Nascimento</label>
                        <div class="input-group date" id="datetimepicker1" data-target-input="nearest">
                            <input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1"
                                th:field="*{dataNascimento}"/>
                            <div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
                                <div class="input-group-text"><i class="fa fa-calendar"></i></div>
                            </div>
                        </div>
                    </div>
                   
                    <div class="form-group">
                        <label for="txtCPF">CPF</label>
                        <input type="text" class="form-control" id="txtCPF" placeholder="CPF" th:field="*{CPF}">
                    </div>
                   
                   
                     
                    <button type="submit" class="btn btn-primary">Enviar</button>
                </form>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
        <script type="text/javascript">
            $(function () {
                $('#datetimepicker1').datetimepicker({
                    format:"DD/MM/YYYY HH:mm"                  
                });
            });
        </script>
        <script type="text/javascript">
            $(function () {
                $("#txtCPF").mask("999.999.999-99");
            });
        </script>
    </body>
</html>

Vincular duas instâncias de objetos em uma tela de cadastro através de uma lista de seleção

Muito conhecido no mundo relacional como chave estrangera, esse tipo de campo tem por objetivo vincular dois registros de tabelas diferentes em um banco de dados. No mundo orientado a objetos chamamos esse tipo de situação de associação, quando uma instância de uma classe possui a referência para a instância de outra classe. Para exemplificar isso, vamos criar uma nova classe chamada Cidade e vamos associá-la a classe Paciente para indicar a cidade de origem do paciente. Portanto na tela de cadastro do Paciente haverá uma lista de cidades a qual o usuário deverá selecionar. Para simplificar o exemplo não vamos criar a tela de cadastro da Cidade, vamos apenas implementar o código necessário para cria a entidade e seu repositório, e então vamos inserir alguns registros na tabela cidade diretamente no banco de dados.

Para dar início no pacote model, vamos criar uma nova classe Cidade com os atributos id, nome e estado. E em seguida vamos inserir as anotações necessárias para realizar o mapeamento objeto relacional desta classe.

package br.com.faltoupontoevirgula.projetospring.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Cidade {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String nome;
    private String estado;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }
    public String getEstado() {
        return estado;
    }
    public void setEstado(String estado) {
        this.estado = estado;
    }
}

Aproveitando que estamos trabalhando no pacote model, vamos alterar o código da classe Paciente, conforme o exemplo abaixo:

  • Nas linhas 32 e 33 – inserimos um novo atributo do tipo Cidade, e colocamos a anotação @ManyToOne, que indica que muitos pacientes podem possuir a mesma cidade
  • Nas linhas 35 à 40 – criamos os métodos GET/SET para o atributo cidade
package br.com.faltoupontoevirgula.projetospring.model;

import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Paciente {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    @NotNull
    @Length(min=2, max=1000, message="O tamanho do nome deve ser entre {min} e {max}")
    private String nome;
    private String sexo;
    @Temporal(value=TemporalType.TIMESTAMP)
    @DateTimeFormat(pattern = "dd/MM/yyyy H:mm")
    private Date dataNascimento;
    private String CPF;
   
    @ManyToOne(cascade= {CascadeType.REFRESH,CascadeType.MERGE})
    private Cidade cidade = new Cidade();
   
    public Cidade getCidade() {
        return cidade;
    }
    public void setCidade(Cidade cidade) {
        this.cidade = cidade;
    }
    public Date getDataNascimento() {
        return dataNascimento;
    }
    public void setDataNascimento(Date dataNascimento) {
        this.dataNascimento = dataNascimento;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNome() {
        return nome;
    }
    public void setNome(String nome) {
        this.nome = nome;
    }
    public String getSexo() {
        return sexo;
    }
    public void setSexo(String sexo) {
        this.sexo = sexo;
    }
    public String getCPF() {
        return CPF;
    }
    public void setCPF(String cPF) {
        CPF = cPF;
    }
}

Para ter acesso aos dados das cidades, e construir a lista de cidades no cadastro de paciente, vamos no pacote repository, criar a interface CidadeRepository conforme o exemplo abaixo.

package br.com.faltoupontoevirgula.projetospring.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import br.com.faltoupontoevirgula.projetospring.model.Cidade;

@Repository
public interface CidadeRepository extends JpaRepository<Cidade, Long>{

}

Agora precisamos alterar o código da tela de cadastro de Paciente, para incorporar o campo Cidade no cadastro do Paciente. Vamos iniciar pela classe de controller do Paciente conforme o exemplo abaixo:

  • Nas linhas 32 e 33 – inserimos um novo atributo para o repositório CidadeRepository, para ter acesso aos dados da tabela no banco de dados
  • Nas linhas 41 à 45 – alteramos o código do método createForm. Esse método é chamado toda vez que o usuário digita na URL /paciente/novo para cadastrar um novo paciente. Por esse motivo precisamos além de carregar o formulário form.html, necessitamos também recuperar do banco de dados a lista de cidades e transferir para a interface. Para isso alteramos o retorno do método createForm para o tipo ModelAndView, na linha seguinte utilizamos o repositório da classe Cidade para executar o método findAll() e salvar o resultado na variável listaCidades. Por fim alteramos o retorno deste método para devolver uma nova instância da classe ModelAndView e dentro dela uma propriedade chamada “listacidades” e a variável listaCidades que contém o resultado da consulta no banco de dados.
  • Nas linhas 53 à 61 – alteramos o código do método alterarForm, pois quando o usuário acessa a opção alterar para qualquer registro também precisamos carregar a lista de Cidades. Para isso dentro deste método executamos o findAll() do repositório da classe Cidade para recuperar a lista de cidades do banco de dados. Em seguida criamos um objeto chamado HashMap, esse objeto é semelhante a uma lista ou array porém ele carrega em cada posição dois valores, sendo que um representa uma CHAVE e o outro o valor. Nas linhas 57 e 58 inserimos dois pares de valores no HashMap (utilizando o método put), primeiro a instância contendo os dados do paciente que será alterado, e em seguida a lista de cidades. Por fim alteramos a instância da classe ModelAndView para retornar a variável dados que contém nosso mapa de valores.
package br.com.faltoupontoevirgula.projetospring.controller;

import java.util.HashMap;
import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import br.com.faltoupontoevirgula.projetospring.model.Cidade;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.repository.CidadeRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;


@Controller
@RequestMapping("/paciente")
public class PacienteController {
   
    @Autowired
    private PacienteRepository pacienteRepository;
   
    @Autowired
    private CidadeRepository cidadeRepository;

    @GetMapping("")
    public ModelAndView index() {
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
       
        return new ModelAndView("paciente/index","listapac",listaPaciente);
    }
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Paciente paciente) {
        List<Cidade> listaCidades = cidadeRepository.findAll();
        return new ModelAndView("paciente/form","listacidades",listaCidades);
    }
   
    @PostMapping(params="form")
    public ModelAndView save(@Valid Paciente paciente, BindingResult result, RedirectAttributes redirect) {
        paciente = this.pacienteRepository.save(paciente);
        return new ModelAndView("redirect:/paciente");
    }
   
    @GetMapping(value="/alterar/{id}")
    public ModelAndView alterarForm(@PathVariable("id") Paciente paciente) {
        List<Cidade> listaCidades = cidadeRepository.findAll();
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("paciente",paciente);
        dados.put("listacidades",listaCidades);
       
        return new ModelAndView("paciente/form",dados);
    }
   
    @GetMapping(value="remover/{id}")
    public ModelAndView remover(@PathVariable ("id") Long id, RedirectAttributes redirect) {
        this.pacienteRepository.deleteById(id);
        return new ModelAndView("redirect:/paciente");
    }
}

Agora que a nossa classe controller está retornando para o formulário a lista das cidades, precisamos alterar o form.html do cadastro de Paciente para incluir um componente de lista que será carregado pelos dados do controller. Para isso alteramos as linhas 39 à 44, onde inserimos um tag SELECT, vinculando seu valor ao atributo cidade da classe Paciente th:field=”*{cidade}”. E dentro do select utilizamos a tag option, mas não colocando valores fixos, mas sim carregando os valores da lista de cidades passada pelo controller:

  • th:each=”umacidade : ${listacidades}” – a propriedade th:each fará a repetição do tag option para cada instância de objeto cidade passado na variável listacidades, sendo que a instância de cada iteração será gravada na nova variável umacidade
  • th:value=”${umacidade.id}” – a propriedade th:value indica o valor interno que representa cada item da lista, como essa informação não é mostrada para o usuário utilizamos o campo id da classe Cidade como identificador único
  • th:text=”${umacidade.nome}” – a propriedade th:text indica o valor que será apresentado ao usuário na lista, para facilitar a escolha apresentamos o atributo nome da classe Cidade>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <nav th:replace="layout :: menu">(menu)</nav>
            <div class="border border-dark">
                <form th:action="@{/paciente/(form)}" th:object="${paciente}" action="#" method="post">
                    <input type="hidden" id="txtid" th:field="*{id}" />
                    <div class="form-group">
                        <label for="txtnome">Nome</label>
                        <input type="text" th:field="*{nome}" class="form-control" id="txtnome" placeholder="Nome">
                    </div>
                    <div class="form-group">
                        <label for="slcsexo">Sexo</label>
                        <select class="form-control" id="slcsexo" th:field="*{sexo}">
                            <option th:value="Masculino">Masculino</option>
                            <option th:value="Feminino">Feminino</option>
                            <option th:value="Outro">Outro</option>
                        </select>
                    </div>
                   
                    <div class="form-group">
                        <label for="slcsexo">Data Nascimento</label>
                        <div class="input-group date" id="datetimepicker1" data-target-input="nearest">
                            <input type="text" class="form-control datetimepicker-input" data-target="#datetimepicker1"
                                th:field="*{dataNascimento}"/>
                            <div class="input-group-append" data-target="#datetimepicker1" data-toggle="datetimepicker">
                                <div class="input-group-text"><i class="fa fa-calendar"></i></div>
                            </div>
                        </div>
                    </div>
                   
                    <div class="form-group">
                        <label for="txtCPF">CPF</label>
                        <input type="text" class="form-control" id="txtCPF" placeholder="CPF" th:field="*{CPF}">
                    </div>
                   
                    <div class="form-group">
                        <label for="slcCidade">Cidade</label>
                        <select class="form-control" id="slcsexo" th:field="*{cidade}">
                            <option th:each="umacidade : ${listacidades}" th:value="${umacidade.id}" th:text="${umacidade.nome}"></option>
                        </select>
                    </div>
                   
                     
                    <button type="submit" class="btn btn-primary">Enviar</button>
                </form>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
        <script type="text/javascript">
            $(function () {
                $('#datetimepicker1').datetimepicker({
                    format:"DD/MM/YYYY HH:mm"                  
                });
            });
        </script>
        <script type="text/javascript">
            $(function () {
                $("#txtCPF").mask("999.999.999-99");
            });
        </script>
    </body>
</html>

Para realizar o teste desta nova funcionalidade, acesso o banco de dados e utilize o comando insert para inserir dois registros na tabela Cidade conforme o exemplo abaixo:

insert into cidade(nome,estado) values(“Joinville”,”SC”);
insert into cidade(nome,estado) values(“Curitiba”,”PR”);

Ao acessar a tela de cadastro de paciente, tanto na opção incluir quanto na opção alterar, o campo cidade será apresentado no formato de lista contendo os registros que foram inseridos diretamente na tabela cidade no banco de dados, como ilustra a figura a seguir:

Para concluir a alteração podemos modificar o código da interface index.html do paciente para que na listagem principal seja apresentada a cidade do paciente que foi cadastrado, conforme exemplo abaixo:

  • Na linha 16 inserimos uma nova coluna chamada Cidade
  • Na linha 26 inserimos uma nova coluna onde a propriedade th:text=”${umpac.cidade.nome}” carrega o nome da cidade que foi vinculada ao paciente
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head th:include="layout :: htmlhead"></head>
    <body>
        <div class="container">
            <nav th:replace="layout :: menu">(menu)</nav>
            <a href="form.html" th:href="@{/paciente/novo}" class="btn btn-primary btn-lg" role="button">Novo</a>
            <div class="border border-dark">
                <table class="table">
                    <thead class="thead-light">
                        <tr>
                            <th scope="col">Nome</th>
                            <th scope="col">Sexo</th>
                            <th scope="col">Data Nascimento</th>
                            <th scope="col">CPF</th>
                            <th scope="col">Cidade</th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr scope="row" th:each="umpac : ${listapac}">
                            <td th:text="${umpac.nome}"></td>
                            <td th:text="${umpac.sexo}"></td>
                            <td th:text="${#dates.format(umpac.dataNascimento, 'dd/MM/yyyy HH:mm')}"></td>
                            <td th:text="${umpac.CPF}"></td>
                            <td th:text="${umpac.cidade.nome}"></td>
                            <td>
                                <a href="form.html" th:href="@{'/paciente/alterar/' + ${umpac.id}}" class="btn btn-secondary btn-lg" role="button">Alterar</a>
                                <a href="#" class="btn btn-warning btn-lg" role="button" data-toggle="modal" data-target="#confirmaExclusao" th:data-id="${umpac.id}" rel="noopener noreferrer">Excluir</a>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <div class="modal fade" id="confirmaExclusao" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
                <div class="modal-dialog" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">Confirmação de exclusão</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">X</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <p>Confirma a exclusão deste registro do Paciente?</p>
                        </div>
                        <div class="modal-footer">
                            <a href="#" class="btn btn-warning btn-lg" id="btnConf" role="button">Sim</a>
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
        <script th:inline="javascript">
          /*<![CDATA[*/
               $('#confirmaExclusao').on('show.bs.modal', function (event) {
                  var button = $(event.relatedTarget)
                  var recipient = button.data('id')
                  var modal = $(this)
                  modal.find('#btnConf').attr("href","/paciente/remover/" + recipient)
                })
           /*]]>*/
        </script>
    </body>
</html>