Cadastro Pai-Filho com 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 objetivo deste tutorial é guiar uma forma simples de utilizar o Spring Boot para desenvolver uma funcionalidade muito comum em sistemas de gestão, o cadastro Pai-Filho. Esse tipo de rotina é normalmente utilizada quando temos um objeto pai que é composto por associação com diversos filhos, sendo o exemplo mais comum em sistemas de gestão empresarial rotinas de abertura de pedido. O pedido é geralmente um objeto que contém informações gerais como data de abertura, data de entrega, o cliente que solicitou esse pedido, e também possui uma lista de itens de pedido. Esses itens são representados por objetos, e neles temos o detalhamento dos produtos que compõe o pedido, além da quantidade e outros valores que estão diretamente.

Para esse tutorial, vamos utilizar o diagrama de classes que já foi utilizado nos demais exemplos. Conforme a figura a seguir, vamos nos concentrar em duas classes principais a Consulta que será a classe PAI que esta ligada por uma lista à classe ProcedimentoRealizado que fará o papel de classe FILHA.

A classe ProcedimentoRealizado assume o papel de classe associativa entre Consulta e Procedimento, o que gera a dúvida, porque não ligar diretamente a Consulta ao Procedimento? O principal motivo é permitir que exista uma classe que associe a Consulta aos Procedimentos, e que armazene informações que tem importância apenas no momento da associação das duas, como exemplo o valor que será cobrado pelo procedimento. Como o valor pode variar, apenas no momento da associação eu esta informação pode ser armazenada.

Para melhor compreender o que será desenvolvido, vamos ver primeiro o resultado final do desenvolvimento. A tela a seguir apresenta o cadastro de consultas médicas.

Ao clicar no botão novo ou no botão alterar, a aplicação nos redireciona para o formulário. Podemos observar que em vermelho temos os dados que compõe a classe Pai (Consulta), e dentro do mesmo formulário temos os dados da classe Filha (Procedimento Realizado).

Ainda falando sobre esse formulário, observe que na região do formulário que trata dos dados da Consulta, temos os campos Médico e Paciente que são outras classes que compõe o sistema e que associam com a Consulta. Da mesma forma na área do ProcedimentoRealizado temos um campo que associa com a classe Procedimento.

Toda vez que o usuário acessar esse formulário, preencher os dados da Consulta, e então na área de vincular procedimento ele selecione um procedimento, informe um valor e clique no botão Inserir Item, uma nova instância da classe ProcedimentoRealizado, o procedimento selecionado será vinculado, e a nova instância da classe será inserida dentro da lista que existe na Consulta. E por esse motivo que a tabela no final do formulário apresenta a lista de procedimentos que foram realizados com seus valores.

Para dar início ao desenvolvimento, vamos criar um conjunto de interfaces no pacote repository, que nos darão acesso aos dados no banco de dados das classes Consulta, Médico, Procedimento. Conforme os exemplos abaixo implemente uma interface repository para a classe PAI, e para todas as classes associadas a classe PAI e FILHA. IMPORTANTE: não é necessário criar um repositório para a classe FILHA.

package br.com.faltoupontoevirgula.projetospring.repository;

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

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

@Repository
public interface ConsultaRepository extends JpaRepository<Consulta, Long>{

}
package br.com.faltoupontoevirgula.projetospring.repository;

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

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

@Repository
public interface MedicoRepository extends JpaRepository<Medico, Long> {

}
package br.com.faltoupontoevirgula.projetospring.repository;

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

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

@Repository
public interface ProcedimentoRepository extends JpaRepository<Procedimento, Long>{

}

O próximo passo é dentro do pacote controller criar a nova classe que irá gerenciar essa funcionalidade, vamos chamar a classe de ConsultaController, e iniciá-la com o seguinte código:

package br.com.faltoupontoevirgula.projetospring.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import br.com.faltoupontoevirgula.projetospring.model.Consulta;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
}

Observe que a classe ConsultaController possui apenas as seguintes linhas de código:

  • Nas linha 20 à 27 – criamos uma variável para cada um dos repositórios de dados que serão utilizados nos formulários para carregar os campos de tela.
  • Nas linhas 29 à 34 – criamos nosso primeiro método chamado index, ele será chamado pela tela principal da rota /consulta e irá apresentar a lista de consultas cadastradas no sistema.

Agora que o controlador foi criado, precisamos criar a interface (view) que será gerenciada por ele, para isso vamos criar uma nova pasta dentro do pacote resources -> templates. Clique com o botão direito do mouse sobre a pasta templates, selecione new -> folder e crie uma nova pasta com o nome de consulta, conforme imagem abaixo.

Dentro da nova pasta consulta que foi criada, vamos criar nosso arquivo html que representará a tela principal. Para isso clique com o botão direito do mouse sobre a pasta consulta, selecione no menu New -> File.
Na próxima tela, informe o nome do arquivo index.html e confirme no botão finish.

Como resultado ilustrado pela próxima figura, teremos o novo arquivo da interface dentro da pasta consulta.

O código fonte que ser utilizado inicialmente dentro do arquivo index.html esta na listagem a seguir. Importante destacar as seguintes linhas:

  • Na linha 7 – já inserimos um botão que irá direcionar o usuário para a rota /consulta/novo para incluir uma nova consulta.
  • Na linha 19 – utilizamos a propriedade th:each=”umacons : ${listaConsulta}” que repete as linhas da tabela HTML para cada instância da classe Consulta recebida pela variável listaConsulta do controller, e grava a instância na variável umacons.
  • Na linha 20 – inserimos a primeira coluna da tabela que será a data cadastrada para a consulta, #dates.format(umacons.data, ‘dd/MM/yyyy HH:mm’), utilizamos o método #dates.format que formata a data no padrão do Brasil dd/MM/yyyy HH:mm
  • Na linha 21 – inserimos a segunda coluna que é o nome médico responsável pela consulta, ${umacons.medicoResponsavel.nome}, caso você observe a classe Consulta vai verificar que ela não possui o nome do médico, mas uma associação para a classe médico, por isso que utilizamos o operador ponto para “de uma consulta” umacons . “recuperar a instância da classe médico associada a consulta” medicoResponsavel. “recuperar o nome do médico” nome
  • Na linha 22 – inserimos a terceira coluna da tabela que é o nome do paciente, onde utilizamos o mesmo mecanismo com o operador ponto para recuperar o nome do paciente associado à consulta ${umacons.paciente.nome}
<!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="@{/consulta/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">Data</th>
                            <th scope="col">Médico</th>
                            <th scope="col">Paciente</th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr scope="row" th:each="umacons : ${listaConsulta}">
                            <td th:text="${#dates.format(umacons.data, 'dd/MM/yyyy HH:mm')}"></td>
                            <td th:text="${umacons.medicoResponsavel.nome}"></td>
                            <td th:text="${umacons.paciente.nome}"></td>
                            <td>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
        <div th:include="layout :: footer" id="footer">(footer)</div>
    </body>
</html>

Agora que a interface index.html da consulta esta pronta, podemos começar a construir nosso formulário que irá permitir o cadastro tanto do objeto PAI (Consulta) quanto os objetos FILHO (ProcedimentoRealizado). Para isso vamos retornar ao controlador ConsultaController e criar o método createForm ligado a rota /consulta/novo, conforme o exemplo do código a seguir:

  • Nas linhas 44 à 46 – utilizamos os repositórios das classes Médico, Paciente e Procedimento para realizar uma consulta no banco de dados e retornar três listas, uma para cada tipo de objeto, com todos os registros do banco de dados. Essas listas serão enviadas para a interface para preencher os componentes da tela permitindo ao usuário selecionar qual médico, paciente e procedimento será cadastrado na consulta.
  • Nas linhas 47 à 50 – construímos um objeto capas de transportar para a tela as listas de médicos, pacientes e procedimentos que foram recuperadas do banco de dados no código anterior. Para isso criamos uma instância da class HashMap, esta classe funciona de forma similar a uma lista, mas em cada linha ela permite armazenar dois valores que recebem nomes específicos, o primeiro é uma chave e o segundo é o valor. Neste caso utilizamos a chave para informar o nome da variável que será utilizada na tela para HTML para acessar os dados, e o segundo parâmetro o nome da lista que foi carregada com dados pelo repositório.
  • Na linha 52 – criamos a instância do nosso objeto ModelAndView para que a interface form.html seja carregada e todos os valores na variável dados sejam transferidos para ela.
package br.com.faltoupontoevirgula.projetospring.controller;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import br.com.faltoupontoevirgula.projetospring.model.Consulta;
import br.com.faltoupontoevirgula.projetospring.model.Medico;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.model.Procedimento;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
       
        return new ModelAndView("consulta/form",dados);
    }
}

Em seguida devemos criar a interface gráfica (view) do formulário para isso, clique com o botão direito sobre o pacote resources -> templates -> consulta e clique na opção NEW -> File. Informe o nome do arquivo como form.html e confirme no botão Finish.

Vamos iniciar construindo apenas a parte do formulário responsável pelo cadastro dos dados da classe PAI (Consulta), em seguida vamos modificar o código para incluir o cadastro da classe FILHO. Conforme o exemplo abaixo, o código é baseado nas demais telas de formulário de cadastro, importante observar que:

  • Na linha 8 – inserimos o tag FORM apontando a action para a rota /consulta utilizando o método POST
  • Na linha 9 – utilizamos a propriedade th:object=”${consulta}”
  • Nas linhas de 11 à 20 – inserimos o primeiro campo que é a data e a hora da consulta. Para facilitar o processo de digitação do usuário, utilizamos o componente de calendário. Para utilizar esse componente é necessário adicionar algumas dependências ao arquivo pom.xml, o detalhamento desse processo de configuração pode ser encontrado AQUI!!!
  • Nas linhas de 22 à 27 – inserimos o campo de seleção para que o usuário informe qual médico será responsável pela consulta. Para isso utilizamos o campo SELECT do HTML que irá renderizar uma lista, os itens da lista são carregados através da propriedade th:each=”umMedico : ${listaMedico}” pela variável listaMedico que foi carregada pelo controlador. O tag option será repetido para cada instância da classe médico que vir na variável listaMedico. A propriedade th:value=”${umMedico.id}” irá utilizar o valor do ID da classe médico para identificar cada item da lista, e a propriedade th:text=”${umMedico.nome}” irá utilizar o nome do médico como o texto que será apresentado para o usuário. Por fim no tag select utilizamos a propriedade th:field=”${consulta.medicoResponsavel}” para vincular esse campo ao atributo medicoResponsavel da classe consulta.
  • Nas linhas 29 à 34 – inserimos o campo de seleção para que o usuário informe qual paciente será atendido na consulta. O funcionamento deste código é similar ao campo de seleção do médico.
  • Nas linhas 36 à 43 – inserimos o campo de seleção para que o usuário informe o status da consulta, neste caso as opções que o usuário terá são fixas e por esse motivo não são consultadas no banco de dados. Apenas na linha 38 utilizamos a propriedade th:field=”${consulta.status}” para vincular o campo de seleção ao atributo status da classe Consulta.
<!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="@{/consulta}"  action="#" method="post">
                    <div th:object="${consulta}">                  
                        <input type="hidden" id="txtid" th:field="${consulta.id}" />
                        <div class="form-group">
                            <label for="datetimepicker1">Data da Consulta</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="${consulta.data}"/>
                                <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="slcMedico">Médico</label>
                            <select class="form-control" id="slcMedico" th:field="${consulta.medicoResponsavel}">
                                <option th:each="umMedico : ${listaMedico}" th:value="${umMedico.id}" th:text="${umMedico.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcPaciente">Paciente</label>
                            <select class="form-control" id="slcPaciente" th:field="${consulta.paciente}">
                                <option th:each="umPaciente : ${listaPaciente}" th:value="${umPaciente.id}" th:text="${umPaciente.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcstatus">Status</label>
                            <select class="form-control" id="slcstatus" th:field="${consulta.status}">
                                <option th:value="Agendado">Agendado</option>
                                <option th:value="Atendimento">Atendimento</option>
                                <option th:value="Finalizado">Finalizado</option>
                            </select>
                        </div>
                    </div>
                    <button type="submit" name="save" 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>

O resultado deverá ser similar a seguinte tela, ao acessar a rota /consultar e clicar no botão Novo, deverá ser apresentado o formulário conforme a imagem a seguir.

Para que o formulário possa realizar a ação de inclusão, basta agora alterar a classe ConsultaController para incluir o método SAVE. Conforme o código a seguir:

  • Na linha 62 à 66 – inserimos o novo método save que foi anotado com @PostMapping(params= {“save”}), que significa que este método será chamado toda vez que uma requisição POST for enviada para a rota /consulta e exista um parâmetro com o nome save. Se você observar o formulário o botão ENVIAR possui uma propriedade name com o valor “save” que é justamente o parâmetro esperado pela anotação do método. Em seguida utilizamos o consultaRepository para executar o método save da variável consulta que possui a instância do objeto contendo todos os dados que foram digitados no formulário. E por fim, o usuário é redirecionado para a tela index deste cadastro.
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.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.Consulta;
import br.com.faltoupontoevirgula.projetospring.model.Medico;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.model.Procedimento;
import br.com.faltoupontoevirgula.projetospring.model.ProcedimentoRealizado;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"save"})
    public ModelAndView save(@Valid Consulta consulta, @Valid ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        consulta = this.consultaRepository.save(consulta);
        return new ModelAndView("redirect:/consulta");
    }
}

Caso você execute a inserção de um registro no formulário e em seguida acesse o banco de dados e consulte a tabela que representa a classe Consulta, verificará que os campos do médico e do paciente contém como chave estrangeira a chave primária das tabelas médico e paciente. Quem faz o trabalho de transformar a referência de associação entre as classes Consulta e Médico e Consulta e Paciente é o JPA/Hibernate o qual usamos através dos repositórios do Spring Boot.

Agora vamos alterar nosso formulário para incluir a parte necessária para que os dados da classe FILHO possam ser cadastrados. Para fazer o processo por etapas, vamos dividir em duas partes:

  • Na primeira parte, vamos criar um subformulário (formulário dentro de outro formulário) com os dados necessários para cadastrar um único ProcedimentoRealizado. Portanto será um fomulário, abaixo do já existente, apenas com os campos: uma lista para selecionar um procedimento, um campo de valor e um botão para inserir esse novo item.
  • Na segunda parte, vamos criar uma listagem com todas as instâncias da classe ProcedimentoRealizado já vinculadas a uma Consulta.

Altere o código fonte do arquivo form.html conforme o exemplo descrito abaixo:

  • Na linha 45, 46 e 47 – utilizamos um componente do Bootstrap chamado CARD que é similar ao conceito de paineis para criar uma divisão dentro do formulário. As três divs que foram criadas, devem ser fechadas no final deste trecho de código nas linhas 63,64 e 65.
  • Na linha 48 – inserimos uma nova div para agrupar os campos do formulário e aplicamos a propriedade th:object=”${novoprocrealizado}” que indica para o Thymeleaft que essa variável novoprocrealizado será carregada pelo controlador com uma instância nova da classe ProcedimentoRealizado e que todos os campos que estão dentro desta DIV fazem parte desse objeto.
  • Nas linhas 50 à 54 – inserimos uma lista de seleção que será carregada com todas as instâncias da classe Procedimento. Esse processo é similar aos campos médico e paciente.
  • Nas linhas 55 à 60 – inserimos um campo para que o usuário possa digitar o valor do procedimento e esse será ligado através da propriedade th:field=”${novoprocrealizado.valor}” ao atributo valor da classe ProcedimentoRealizado.
  • Nas linhas 61 à 63 – inserimos o botão Inserir Item que possui uma propriedade name=”insertproc” para que o controller possa diferenciar quando este botão ou o botão enviar do formulário completo for acionado.
<!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="@{/consulta}"  action="#" method="post">
                    <div th:object="${consulta}">                  
                        <input type="hidden" id="txtid" th:field="${consulta.id}" />
                        <div class="form-group">
                            <label for="datetimepicker1">Data da Consulta</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="${consulta.data}"/>
                                <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="slcMedico">Médico</label>
                            <select class="form-control" id="slcMedico" th:field="${consulta.medicoResponsavel}">
                                <option th:each="umMedico : ${listaMedico}" th:value="${umMedico.id}" th:text="${umMedico.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcPaciente">Paciente</label>
                            <select class="form-control" id="slcPaciente" th:field="${consulta.paciente}">
                                <option th:each="umPaciente : ${listaPaciente}" th:value="${umPaciente.id}" th:text="${umPaciente.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcstatus">Status</label>
                            <select class="form-control" id="slcstatus" th:field="${consulta.status}">
                                <option th:value="Agendado">Agendado</option>
                                <option th:value="Atendimento">Atendimento</option>
                                <option th:value="Finalizado">Finalizado</option>
                            </select>
                        </div>
                    </div>
                    <div class="card">
                        <div class="card-header">Vincluar Procedimentos</div>
                        <div class="card-body">
                            <div class="container" th:object="${novoprocrealizado}">
                              <div class="row">
                                <div class="col-sm">
                                  <select class="form-control" id="slcProcedimentos" th:field="${novoprocrealizado.procedimento}">
                                    <option th:each="umProc : ${listaProcedimento}" th:value="${umProc.id}" th:text="${umProc.descricao}"></option>
                                  </select>
                                </div>
                                <div class="col-sm">
                                  <label for="txtvalor">Valor</label>
                                </div>
                                <div class="col-sm">
                                  <input type="text" class="form-control" id="txtvalor" th:field="${novoprocrealizado.valor}" placeholder="Valor">
                                </div>
                                <div class="col-sm">
                                    <button type="submit" name="insertproc" class="btn btn-primary">Inserir item</button>
                                </div>
                              </div>
                            </div>
                            <br>                           
                        </div>
                    </div>
                    <button type="submit" name="save" 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>

Como a conclusão desta primeira parte, precisamos modificar novamente nossa classe ConsultaController para que a nova instância da classe ProcedimentoRealizado seja passada para a interface. A alteração é bastante simples, apenas inserimos a linha 57 no método createForm inserimos um novo dado em nosso HashMap chamado “novoprocrealizado” que é exatamente o nome da propriedade que foi atribuída aos campos no arquivo form.html. Essa variável recebe uma nova instância da classe ProcedimentoRealizado, assim ela sempre estará pronta para receber novos dados.

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.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.Consulta;
import br.com.faltoupontoevirgula.projetospring.model.Medico;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.model.Procedimento;
import br.com.faltoupontoevirgula.projetospring.model.ProcedimentoRealizado;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"save"})
    public ModelAndView save(@Valid Consulta consulta, @Valid ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        consulta = this.consultaRepository.save(consulta);
        return new ModelAndView("redirect:/consulta");
    }
}

O resultado desta modificação é que ao acessar a opção Novo do cadastro de consulta, uma tela similar será apresentada, já permitindo selecionar os procedimentos e o campo de valor virá com 0.0 para ser alterado.

IMPORTANTE: como a classe Médico e Procedimento não tiveram suas telas de cadastro implementadas, foi necessário acessar o banco de dados e realizar inserts manuais nas tabelas para que os dados pudessem ser apresentados neste cadastro.

Vamos então inserir o código necessário para implementar a segunda parte do cadastro FILHO, que irá listar as instâncias da classe ProcedimentoRealizado já vinculadas à consulta.

O próximo passo é alterar novamente nosso arquivo form.html, para incluir a listagem de instâncias da classe ProcedimentoRealizado:

  • Na linha 67 – incluímos uma nova tag do tipo table, para desenhar a listagem dos objetos
  • Nas linhas 68 à 74 – incluímos os cabeçalhos da tabela
  • Nas linhas 75 à 85 – inserimos os tags necessários para que o conteúdo da listagem seja carregado automaticamente, mas neste código faz-se necessário um detalhamento linha a linha de seu funcionamento.
  • Na linha 76 – incluímos a tag TR que será repetida para cada instância da classe ProcedimentoRealizado devido a utilização da propriedade th:each=”procreali, stat : ${consulta.listaProcedimentos}”. Os dados da lista de ProcedimentosRealizados virão do objeto Consulta conforme o código ${consulta.listaProcedimentos}, isso acontece porque estamos no formulário de inclusão/alteração da consulta, e esta classe possui uma lista de procedimentos realizados que se associa a ela. Para cada instância que for encontrada, sua referência será armazenada na variável procreali e um número sequencial será gravado na variável stat, representando a ordem dos objetos.
  • Na linha 77 e 78 – utilizamos um mecanismo do Thymeleaf que gera campos dinâmicos. Como o usuário terá a opção de incluir e excluir diversas linhas nesta listagem, precisamos que cada linha desta tabela seja armazenada em memória para isso utilizamos uma propriedade th:field=”${consulta.listaProcedimentos[__${stat.index}__].procedimento}” para cada atributo do objeto ProcedimentoRealizado assim os dados digitados em tela não serão perdidos.
  • Nas linhas 79 e 80 – inserimos efetivamente as colunas da tabela apresentando na linha 79 o nome do procedimento que foi vinculado ao procedimento realizado dentro da consulta, e na linha 80 o valor do procedimento realizado vinculado a consulta
  • Nas linhas 81 à 83 – inserimos os tags necessários para incluir um novo botão ao lado de cada linha da tabela, para permitir a remoção de um procedimento realizado, caso tenha sido inserido de forma indevida. Importante que este botão carrega com sigo a propriedade th:value=”__${stat.index}__” que irá passar para o controlador a posição do item na lista de ProcedimentoRealizado que se deseja excluir.
<!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="@{/consulta}"  action="#" method="post">
                    <div th:object="${consulta}">                  
                        <input type="hidden" id="txtid" th:field="${consulta.id}" />
                        <div class="form-group">
                            <label for="datetimepicker1">Data da Consulta</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="${consulta.data}"/>
                                <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="slcMedico">Médico</label>
                            <select class="form-control" id="slcMedico" th:field="${consulta.medicoResponsavel}">
                                <option th:each="umMedico : ${listaMedico}" th:value="${umMedico.id}" th:text="${umMedico.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcPaciente">Paciente</label>
                            <select class="form-control" id="slcPaciente" th:field="${consulta.paciente}">
                                <option th:each="umPaciente : ${listaPaciente}" th:value="${umPaciente.id}" th:text="${umPaciente.nome}"></option>
                            </select>
                        </div>
                       
                        <div class="form-group">
                            <label for="slcstatus">Status</label>
                            <select class="form-control" id="slcstatus" th:field="${consulta.status}">
                                <option th:value="Agendado">Agendado</option>
                                <option th:value="Atendimento">Atendimento</option>
                                <option th:value="Finalizado">Finalizado</option>
                            </select>
                        </div>
                    </div>
                    <div class="card">
                        <div class="card-header">Vincluar Procedimentos</div>
                        <div class="card-body">
                            <div class="container" th:object="${novoprocrealizado}">
                              <div class="row">
                                <div class="col-sm">
                                  <select class="form-control" id="slcProcedimentos" th:field="${novoprocrealizado.procedimento}">
                                    <option th:each="umProc : ${listaProcedimento}" th:value="${umProc.id}" th:text="${umProc.descricao}"></option>
                                  </select>
                                </div>
                                <div class="col-sm">
                                  <label for="txtvalor">Valor</label>
                                </div>
                                <div class="col-sm">
                                  <input type="text" class="form-control" id="txtvalor" th:field="${novoprocrealizado.valor}" placeholder="Valor">
                                </div>
                                <div class="col-sm">
                                    <button type="submit" name="insertproc" class="btn btn-primary">Inserir item</button>
                                </div>
                              </div>
                            </div>
                            <br>                           
                            <table class="table">
                                <thead class="thead-light">
                                    <tr>
                                        <th scope="col">Procedimento</th>
                                        <th scope="col">Valor</th>
                                        <th scope="col"></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr scope="row" th:each="procreali, stat : ${consulta.listaProcedimentos}">
                                        <input type="hidden" th:field="${consulta.listaProcedimentos[__${stat.index}__].procedimento}"/>
                                        <input type="hidden" th:field="${consulta.listaProcedimentos[__${stat.index}__].valor}"/>
                                        <td th:text="${procreali.procedimento.descricao}"></td>
                                        <td th:text="${procreali.valor}"></td>
                                        <td>
                                            <button type="submit" name="removeproc" th:value="__${stat.index}__" class="btn btn-primary">Remover</button>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                    <button type="submit" name="save" 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>

Agora que nosso formulário PAI/FILHO esta completo, precisamos modificar o código da classe ConsultaController para permitir que os dados da classe filho ProcedimentoRealizado possam ser armazenadas. Dois métodos foram criados:

  • Nas linhas 68 à 85 – criamos um método chamado insertproc, esse método será chamado pelo botão Inserir Item pois em sua anotação ele espera o parâmetro @PostMapping(params= {“insertproc”}) igual ao name do botão. Esse método recebe por parâmetro a instância da classe Consulta e do novo ProcedimentoRealizado, então ele além de buscar as listas de objetos necessárias para alimentar o formulário, ele na linha 74 insere o novo ProcedimentoRealizado dentro da Consulta. Todos os dados são retornados para o form que irá desenhar o novo procedimento na tabela.
  • Nas linhas 87 à 104 – criamos o método chamado removeproc, esse método será chamado pelo botão remover, pois em sua anotação ele espera o parâmetro @PostMapping(params= {“removeproc”}) igual ao atributo name do botão. Esse método recebe por parâmetro a posição na lista de ProcedimentoRealizados do item que se deseja remover, a instância da classe consulta que esta sendo modificada pela tela de cadastro, e como os demais métodos a instância do novo ProcedimentoRealizado que neste caso não será utilizada. Na linha 94 ocorre o processo de remoção do item da lista de ProcedimentoRealizado, então os demais dados que compõe a tela são retornados para a view.
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import br.com.faltoupontoevirgula.projetospring.model.Consulta;
import br.com.faltoupontoevirgula.projetospring.model.Medico;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.model.Procedimento;
import br.com.faltoupontoevirgula.projetospring.model.ProcedimentoRealizado;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"save"})
    public ModelAndView save(@Valid Consulta consulta, @Valid ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        consulta = this.consultaRepository.save(consulta);
        return new ModelAndView("redirect:/consulta");
    }
    @PostMapping(params= {"insertproc"})
    public ModelAndView insertproc(Consulta consulta, ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        consulta.getListaProcedimentos().add(novoprocrealizado);
   
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("consulta", consulta);
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"removeproc"})
    public ModelAndView removeproc(@RequestParam(name = "removeproc") int index, Consulta consulta, ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        consulta.getListaProcedimentos().remove(index);
   
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("consulta", consulta);
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
       
        return new ModelAndView("consulta/form",dados);
    }
}

Antes de realizar o teste da nova funcionalidade, precisamos fazer uma pequena alteração na classe Consulta do pacote model do nosso projeto. Conforme já foi explicado a classe Consulta faz o papel de classe PAI, portanto dentro dela temos a associação com a classe ProcedimentoRealizado através do atributo listaProcedimentos. Alteramos a linha 38 deste código para que na anotação @OneToMany que associa as duas classes, para inserir uma nova propriedade que é orphanRemoval=true. Com isso, toda vez que um ProcedimentoRealizado que já estava vinculado a uma Consulta for removido da lista, automaticamente o JPA realizará a exclusão dele do banco de dados.

package br.com.faltoupontoevirgula.projetospring.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.springframework.format.annotation.DateTimeFormat;

@Entity
public class Consulta {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    @DateTimeFormat(pattern = "dd/MM/yyyy H:mm")
    @Temporal(value=TemporalType.TIMESTAMP)
    private Date data;
    @Column(length=10000)
    private String status;
   
    @ManyToOne(cascade= {CascadeType.MERGE,CascadeType.REFRESH})
    private Paciente paciente;
   
    @ManyToOne(cascade= {CascadeType.MERGE,CascadeType.REFRESH})
    private Medico medicoResponsavel;
   
    @OneToMany(cascade=CascadeType.ALL,orphanRemoval=true)
    @JoinColumn(name="consulta_id")
    private List<ProcedimentoRealizado> listaProcedimentos = new ArrayList<ProcedimentoRealizado>();

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Date getData() {
        return data;
    }

    public void setData(Date data) {
        this.data = data;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public Paciente getPaciente() {
        return paciente;
    }

    public void setPaciente(Paciente paciente) {
        this.paciente = paciente;
    }

    public Medico getMedicoResponsavel() {
        return medicoResponsavel;
    }

    public void setMedicoResponsavel(Medico medicoResponsavel) {
        this.medicoResponsavel = medicoResponsavel;
    }
   
    public List<ProcedimentoRealizado> getListaProcedimentos() {
        return listaProcedimentos;
    }

    public void setListaProcedimentos(List<ProcedimentoRealizado> listaProcedimentos) {
        this.listaProcedimentos = listaProcedimentos;
    }
}

Com estas modificações já será possível incluir uma nova consulta e vincular Procedimentos a ela, conforme a imagem a seguir.

Para concluir nos falta algora implementar as funcionalidades de ALTERA e EXCLUIR uma consulta na view consulta/index.html. Para isso vamos fazer as seguintes modificações nestes arquivo.

  • Na linha 15 – inserimos uma nova coluna no cabeçalho para incluir os botões de ação.
  • Nas linhas 23 a 26 – inserimos os dois botões ALTERAR que irá realizar uma chamada para a URL /consulta/alterar e o botão excluir que irá abrir a janela de diálogo para confirmação da exclusão.
  • Nas linhas 31 à 49 – criamos a janela de diálogo que apresentará a pergunta de confirmação para a ação de exclusão.
  • Nas linhas 52 à 61 – inserimos o código jQuery responsável por atualizar a propriedade href do link do botão de confirmação de exclusão para chamar a URL /consulta/remove/{id} incluindo o Id da instância da classe Consulta a qual se deseja remover.
<!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="@{/consulta/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">Data</th>
                            <th scope="col">Médico</th>
                            <th scope="col">Paciente</th>
                            <th scope="col"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr scope="row" th:each="umacons : ${listaConsulta}">
                            <td th:text="${#dates.format(umacons.data, 'dd/MM/yyyy HH:mm')}"></td>
                            <td th:text="${umacons.medicoResponsavel.nome}"></td>
                            <td th:text="${umacons.paciente.nome}"></td>
                            <td>
                                <a href="form.html" th:href="@{'/consulta/alterar/' + ${umacons.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="${umacons.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 desta consulta?</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","/consulta/remover/" + recipient)
                })
           /*]]>*/
        </script>
    </body>
</html>

Agora que a interface esta pronta, podemos alterar novamente a classe ConsultaController para incluir os métodos que serão responsáveis por alterar o registro da consulta e excluir o registro:

  • Nas linhas 107 à 121 – criamos o método alterarForm que foi anotado com @GetMapping(value=”/alterar/{id}”), e que será chamado pelo botão alterar da view /consulta/index.html. Recebemos como parâmetro pela URL o id, que é utilizado automaticamente pelo Spring para encontrar a classe Consulta no banco de dados referente a chave primária. Em seguida, assim como foi feito em todos os códigos anteriores, recuperamos as listas dos objetos associados as classes do cadastro, e retornamos todos os dados para serem apresentados pela view form.html
  • Nas linhas 126 à 130 – inserimos o método remover anotado com @GetMapping(value=”remover/{id}”), que é chamado pelo botão de confirmação de exclusão SIM da view /consulta/index.html, e que recebe por parâmetro o id através da URL da requisição. Então o valor do id da classe consulta é passado para o método deleteById do repositório da classe Consulta para exclusão do registro.

IMPORTANTE destacar que: o método altera e o excluir, não fazem referência a classe ProcedimentoRealizado associada a consulta, pois, através do JPA/Hibernate quando o método alterar é chamado para buscar no banco de dados a instância da classe consulta, automaticamente todas as instâncias da classe ProcedimentoRealizado são carregadas do banco de dados. Da mesma forma quando o método remover é chamado para excluir uma instância da classe Consulta, devido a propriedade cascade=CascadeType.ALL colocada no atributo listaProcedimentos dentro da classe model.Consulta, todas as ações realizadas na classe Consulta serão automaticamente propagadas para as instâncias associadas de ProcedimentoRealizado. Portanto ao excluir a Consulta, os ProcedimentosRealizados são excluídos automaticamente.

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.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import br.com.faltoupontoevirgula.projetospring.model.Consulta;
import br.com.faltoupontoevirgula.projetospring.model.Medico;
import br.com.faltoupontoevirgula.projetospring.model.Paciente;
import br.com.faltoupontoevirgula.projetospring.model.Procedimento;
import br.com.faltoupontoevirgula.projetospring.model.ProcedimentoRealizado;
import br.com.faltoupontoevirgula.projetospring.repository.ConsultaRepository;
import br.com.faltoupontoevirgula.projetospring.repository.MedicoRepository;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;
import br.com.faltoupontoevirgula.projetospring.repository.ProcedimentoRepository;

@Controller
@RequestMapping("/consulta")
public class ConsultaController {
    @Autowired
    private ConsultaRepository consultaRepository;
    @Autowired
    private PacienteRepository pacienteRepository;
    @Autowired
    private MedicoRepository medicoRepository;
    @Autowired
    private ProcedimentoRepository procedimentoRepository;
   
    @GetMapping("")
    public ModelAndView index() {
        List<Consulta> listaConsulta = this.consultaRepository.findAll();
       
        return new ModelAndView("consulta/index","listaConsulta",listaConsulta);
    }
   
    @GetMapping("/novo")
    public ModelAndView createForm(@ModelAttribute Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"save"})
    public ModelAndView save(@Valid Consulta consulta, @Valid ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        consulta = this.consultaRepository.save(consulta);
        return new ModelAndView("redirect:/consulta");
    }
    @PostMapping(params= {"insertproc"})
    public ModelAndView insertproc(Consulta consulta, ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        consulta.getListaProcedimentos().add(novoprocrealizado);
   
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("consulta", consulta);
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @PostMapping(params= {"removeproc"})
    public ModelAndView removeproc(@RequestParam(name = "removeproc") int index, Consulta consulta, ProcedimentoRealizado novoprocrealizado, BindingResult result, RedirectAttributes redirect) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        consulta.getListaProcedimentos().remove(index);
   
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("consulta", consulta);
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
       
        return new ModelAndView("consulta/form",dados);
    }
   
    @GetMapping(value="/alterar/{id}")
    public ModelAndView alterarForm(@PathVariable("id") Consulta consulta) {
        List<Medico> listaMedico = this.medicoRepository.findAll();
        List<Paciente> listaPaciente = this.pacienteRepository.findAll();
        List<Procedimento> listaProcedimento = this.procedimentoRepository.findAll();
       
        HashMap<String, Object> dados = new HashMap<String, Object>();
        dados.put("consulta", consulta);
        dados.put("listaMedico", listaMedico);
        dados.put("listaPaciente", listaPaciente);
        dados.put("listaProcedimento", listaProcedimento);
        dados.put("novoprocrealizado", new ProcedimentoRealizado());
       
        return new ModelAndView("consulta/form",dados);
    }
   
   
   
   
    @GetMapping(value="remover/{id}")
    public ModelAndView remover(@PathVariable ("id") Long id, RedirectAttributes redirect) {
        this.consultaRepository.deleteById(id);
        return new ModelAndView("redirect:/consulta");
    }
   
   
}