Desenvolvimento utilizando 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 desta publicação é servir de guia básico para o desenvolvimento de aplicações para internet utilizando o Spring Boot.
Código completo em: https://github.com/waltercoan/ProjetoSpring.git

Sumário

Introdução

Segundo BOAGLIO (2017) “Depois de 18 meses e milhares de commits, saiu a primeira versão do Spring Boot e abril de 2014. Josh Long, desenvolvedor Sprint na Pivotal, afirma que a ideia inicial do Spring Boot veio da necessidade de o Spring Framework ter suporte a servidores web embutidos.” Portanto o Spring Boot se baseia totalmente no Spring Framework (mais sobre sua história pode ser encontrado aqui: https://en.wikipedia.org/wiki/Spring_Framework), mas resumindo este framework Java é considerado um dos mais utilizados para o desenvolvimento de aplicações para internet, devido seus atributos:

  • simplicidade no processo de desenvolvimento
  • adoção do blueprint MVC
  • grande quantidade de bibliotecas adicionais que permitem integrações com outros serviços na internet

O Spring Boot proporcional uma simplificação maior ao processo de desenvolvimento de aplicações com o Spring Framework, uma vez que toda a configuração do ambiente é feita automaticamente por essa nova ferramenta.

Configuração do ambiente

Para a configuração do ambiente de desenvolvimento, vamos primeiramente realizar o download e instalação do JDK Java, que consiste no kit para os desenvolvedores. Acesse o site java.oracle.com, e procure pelo JDK 8 com a versão mais recente, caso tenha dificuldades para encontrar utilize este link: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html. Após realizar o download do instalador do Java JDK, basta executá-lo e seguir o processo de instalação até o fim.  Agora podemos realizar o download da ferramenta Spring Tool Suite através do endereço: https://spring.io/tools. Uma vez concluído o download do arquivo spring-tool-suite-3.9.2.RELEASE-e4.7.2-win32-x86_64.zip, basta descompactá-lo dentro da unidade de disco c: do seu computador (importante não descompacte dentro das pastas as quais o sistema operacional Windows realiza o controle de acesso como arquivo de programas).

Ao descompactar o arquivo, observará que existem três pastas, na pasta que inicia com sts (Spring Tool Suite), temos o executável do Eclipse chamado STS.exe, basta clicar duas vezes sobre ele. Caso tudo ocorra bem, será apresentada a tela inicial do Spring Tool Suite. Como toda primeira vez que o Eclipse é executado, é necessário indicar o diretório da workspace onde os arquivos fonte serão armazenados.

A tela inicial do eclipse, sera apresentada conforme a imagem a seguir, e no canto esquerdo os projetos existentes são listados.


Antes de iniciar a criação do projeto precisamos configurar o Eclipse para utilizar a versão JDK do Java. Para isso clique no menu Windows -> Preferences.

Na próxima tela clique no menu lateral Java -> Installed JREs, então clique no botão ADD.

No primeiro passo, selecione a opção Standard VM e clique no botão Next.

Na última tela, informe no campo JRE Home o caminho completo para sua JDK e confirme no botão Finish.

Ao retornar nesta tela, verifique se o Eclipse listou a JDK, e marque ela como padrão confirmando no botão Apply and Close.

Para concluir retorne novamente ao menu Windows -> Preferences.

Ao retornar a tela de preferencias do eclipse, clique no menu lateral Java -> Installed JRE -> Executation Environments. O Eclipse irá listar as versões de JREs encontradas, selecione JavaSE-1.8 e na lista de compatible JREs, selecione a JDK. Confirme a alteração no botão Apply and Close.

Criando o primeiro projeto

Vamos dar início ao processo de criação do primeiro projeto utilizando Spring Boot, para isso acesse o menu File -> New -> Spring Starter Project.

A próxima tela apresentada é muito importante pois nela que toda a configuração do novo projeto é realizada. No campo Name deve ser informado o nome do projeto, no campo Group e Package devem ter valores semelhantes, devem iniciar com uma URL invertida para informar quem é a empresa responsável pelo desenvolvimento dessa aplicação. Então o campo Package recebe ainda no final o nome do projeto, importante destacar que cada item do campo separado por ponto será representado por uma pasta no projeto final. Acione o botão Next.

Na próxima tela vamos informar as bibliotecas de dependências que serão utilizadas pelo projeto. Vamos selecionar as bibliotecas (importante, caso não apareça na opção de uso frequente você pode usar o campo de busca):

  • Web – biblioteca básica do Spring para construção de aplicações WEB
  • Thymeleaf – bibliteca para construção de templates HTML integrados ao Spring
  • Rest Repositories – biblioteca para construção de API RESTFull
  • JPA – biblioteca Java Persistence API, responsável pelo mapeamento objeto relacional
  • MySQL – biblioteca necessária para comunicação com o banco de dados

O projeto Spring possui uma estrutura semelhante a ilustrada abaixo, sendo que cada arquivo/pacote tem uma função:

  • br.com.faltoupontoevirgula.projetospring – pacote principal onde todo o código Java deve ser inserido
  • ProjetoSpringApplication.java – código principal da aplicação, responsável por carregar as bibliotecas do Spring
  • src/main/resources/templates – pasta onde serão inseridos os templates HTML/Thymeleaf
  • src/main/resources/static – pasta onde serão inseridos arquivos de imagens e folhas de estilo CSS
  • src/test/java – pasta que contém os pacotes e classes de teste JUnit
  • arquivo pom.xml – arquivo da ferramenta Apache Maven, responsável por criar e configurar todo o ambiente da aplicação com suas bibliotecas dependentes.
  • arquivo application.properties – arquivo de configuração da aplicação.


Integração com o banco de dados MySQL

Uma última configuração precisa ser feita, antes de iniciar o desenvolvimento da aplicação. Como incluímos a dependência do pacote JPA precisamos configurar a conexão com o banco de dados MySQL. Para isso vamos editar o arquivo application.properties e incluir as seguintes linhas:

</p>
<p>spring.datasource.url=jdbc:mysql://localhost:3306/dbprojetospring<br />spring.datasource.username=bob<br />spring.datasource.password=bob<br />spring.jpa.hibernate.ddl-auto=create</p>
<p>

Importante: caso ocorra o erro

java.sql.SQLException: The server time zone value ‘Hora oficial do Brasil’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.

Modifique a string de conexão com o banco para:

</p>
<p>spring.datasource.url=jdbc:mysql://localhost:3306/dbprojetospring?useTimezone=true&amp;serverTimezone=UTC<br />spring.datasource.username=bob<br />spring.datasource.password=bob<br />spring.jpa.hibernate.ddl-auto=create</p>
<p>

O spring.datasource.url informa ao Spring qual driver JDBC deverá ser utilizado para conectar com o banco de dados, além da String de conexão com o banco. Os parametros spring.datasource.username e spring.datasource.password indicam o usuário e senha para conectar ao banco de dados. E por fim o spring.jpa.hibernate.ddl-auto=create indica que as tabelas e demais estruturas de dados deverão ser criadas automaticamente pelo provedor de persistência Hibernate. Em seguida vamos compreender sua importância.

Precisamos agora criar um banco de dados no MySQL para que a aplicação possa se conectar a ele. Para isso abra um terminal do windows CMD, e execute o comando:

mysql -u root -p

e em seguida digite a senha padrão do seu MySQL

Execute o comando abaixo para criar o banco de dados dbprojetospring:

create database dbprojetospring;

em seguida execute o comando para criar o usuário bob com a senha bob e dar total permissão no banco de dados:

grant all privileges on dbprojetospring.* to ‘bob’ identified by ‘bob’;

para concluir, precisamos efetivar as alterações de permissão no banco de dados com o comando:

flush privileges;

IMPORTANTE: nas versões mais recentes do MySQL é necessário criar o usuário identificando qual o computador de origem da conexão, para isso execute também a seguinte linha no banco de dados:

grant all privileges on dbprojetospring.* to ‘bob’@’localhost’ identified by ‘bob’;

Em seguida o comando flush novamente:

flush privileges;

Caso o comando GRANT gere erro não permitindo criar o usuário, faça o seguinte processo:

</p>
<p>CREATE USER bob@'localhost' IDENTIFIED  WITH mysql_native_password BY 'bob';</p>
<p>GRANT ALL privileges ON nomedb.* TO bob@'localhost';</p>
<p>FLUSH privileges;</p>
<p>

Mais informações importantes sobre a configuração com o banco de dados pode ser encontradas em: https://spring.io/guides/gs/accessing-data-mysql/

Agora que compreendemos minimamente a estrutura do projeto do Spring Framework e configuramos a conexão com o banco de dados, vamos iniciar o desenvolvimento de um cadastro simples de Paciente. Mas antes vamos entender o funcionamento de um framework que utiliza o padrão MVC (Model – View – Control). Para isso, vamos criar um novo pacote dentro da nossa aplicação, clique com o botão direito do mouse sobre o pacote br.com.faltoupontoevirgula.projetospring -> New -> Package.

Na próxima tela, no campo Name, digite no final do pacote o nome do novo pacote que é .controller Confirme com o botão Finish.

IMPORTANTE: todo o código fonte Java deverá ficar abaixo do pacote principal da aplicação que é br.com.faltoupontoevirgula.projetospring, caso contrário esse código não será considerado pelo framework.

Agora que o novo pacote foi criado, vamos criar uma nova classe que será responsável por controlar a página principal da nossa aplicação. Para isso, clique sobre o pacote controller com o botão direito, e acione a opção New -> Class.

Na próxima tela, informe o nome da classe como HomeController e confirme no botão Finish.

Agora vamos codificar nosso controlador, primeiro vamos colocar a anotação @Controller sobre a declaração da classe HomeController, importante realizar o import desta anotação. Utilize sempre o atalho Ctrl + Shift + Letra O, para solucionar os imports pendentes. Crie um método público index() com retorno String e coloque sobre ele a anotação @RequestMapping(“/”) e @ResponseBody. Dentro do método retorne uma string qualquer.

</p>
<p>package br.com.faltoupontoevirgula.projetospring.controller;</p>
<p>import org.springframework.stereotype.Controller;<br />import org.springframework.web.bind.annotation.RequestMapping;<br />import org.springframework.web.bind.annotation.ResponseBody;</p>
<p>@Controller<br />public class HomeController {</p>
<p>@RequestMapping("/")<br />@ResponseBody<br />public String index() {<br />return "eu não acredito";<br />}<br />}</p>
<p>

Antes de compreender o funcionamento das anotações vamos ver nosso código em execução. Para isso clique com o botão direito do mouse sobre o nome do projeto -> Run As -> Spring Boot App. Esse processo é necessário apenas na primeira vez, nas demais é possível utilizar o botão PLAY do Eclipse para iniciar o servidor.

Ao inicializar o processo, você vai observar que o console do Eclipse irá apresentar o log de execução da aplicação, ele é uma ferramenta importante para entender o que esta acontecendo na aplicação. Importante observar que ele informa no log a porta 8080 onde a aplicação foi carregada.

Por fim basta abrir um navegador e digitar http://localhost:8080, para receber o retorno a seguir:

Para melhor compreender o funcionamento de uma aplicação que utiliza o padrão MVC, ilustramos o processo utilizando o diagrama de componentes da UML. Temos neste estilo arquitetural três componentes principais:

  • Cliente – representado pelo componente Navegador
  • Servidor
    • Representado pela aplicação Spring
    • e pelo servidor de banco de dados

MVC é uma sigla que significa Model – View – Control, portanto nossa aplicação será dividia dentro de pelo menos esses três componentes, cujo objetivos são:

  • Model – são classes que representam os dados que serão manipulados pela aplicação, por esse motivo estão diretamente ligados as regras de negócio da solução;
  • View – são classes ou arquivos HTML que tem por responsabilidade desenhar a interface gráfica que será apresentada ao usuário final como resultado de uma requisição;
  • Control – são classes responsáveis por todo o controle do funcionamento da aplicação.

Quando o usuário digita uma URL no navegador, essa requisição é transmitida pela rede/internet até alcançar o servidor, essa chamada é realizada pelo protocolo HTTP (Hyper Text Transfer Protocol). Quando a requisição chega ao servidor, ela é analisada e o mecanismo do Spring Framework identifica pela URL na requisição, qual controlador possui um RequestMapping que é similar ao solicitado. Então o Controlador é instanciado o método específico é executado. Neste método pode haver a necessidade de consulta de dados do banco de dados, para isso o controlador irá utilizar a camada de persistência através da API JPA e do provedor de persistência Hibernate, que fará a conexão com o banco de dados, a execução de consultas SQL no banco, a recepção dos dados retornados que serão mapeados para objetos de modelo, também conhecidos como entidades. Esses objetos model, serão retornado para o controlador que por sua vez irá utilizá-los para preencher as view que são os componentes responsáveis por criar as interfaces de visualização dos dados. E por fim a view será retornada automaticamente para o navegador cliente como resposta da requisição enviada.

Escrevendo o primeiro teste unitário

Apesar do nosso primeiro código ser muito simples, é importante que a aplicação seja construída considerando sua qualidade. Para isso é fundamental a existência de testes unitários que cubram a maioria do código fonte do programa. Aproveitando que temos ainda um código simples no controlador, vamos implementar a rotina de testes unitário para o método index dentro da classe home. O procedimento de criação dos testes está descrito neste link: https://spring.io/guides/gs/testing-web/

Para isso verifique o conteúdo do arquivo pom.xml se na seção de dependências existe a seguinte tag XML:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

O próximo passo é editar o código da classe ProjetoSpringApplicationTests contida na pasta src/test/java. Conforme o exemplo abaixo na linha 15 e 16 foi criado um atributo de classe do tipo HomeController, e sobre ele foi inserida a anotação @Autowired, esta anotação tem por objetivo buscar o componente dentro da aplicação Spring e instanciá-lo automaticamente. Na linha 21, dentro do método de teste padrão contextLoads(), realizamos o primeiro teste para verificar se a variável controller possui uma instância não nula.

package br.com.faltoupontoevirgula.projetospring;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import br.com.faltoupontoevirgula.projetospring.controller.HomeController;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProjetoSpringApplicationTests {

@Autowired
private HomeController controller;

@Test
public void contextLoads() {
//Verifica a existência da instância do controlador
assertThat(controller).isNotNull();
}

}

Para executar o teste, clique sobre o arquivo fonte java no menu esquerdo com o botão direito do mouse, Run As -> JUnit Test.

Durante o processo de execução do teste, você observará que o Eclipse irá apresentar uma nova aba, contendo o resultado do processo de execução de cada teste conforme a imagem a seguir.

Mas esse teste ainda é bastante inicial, precisamos verificar se o método do nosso controlador esta retornando para o cliente o resultado esperado. Não se assuste com a próxima versão do código, muita coisa mudou, mas vamos entender passo a passo.

  • Linhas 3 à 8 – import static é uma diretiva do Java que permite importar métodos estáticos criados em outras classes para o código onde estamos programando, por esse motivo que podemos executar o método get() sem ter uma variável antes dele com a instância de um objeto;
  • Linha 22 – @AutoConfigureMockMvc essa anotação configura nosso testes para utilizar as classes MockMvc, que são classes que simulam a ação de um cliente acessando nossos sistema, e portanto, permitem testar a reação do programa;
  • Linhas 36 à 41 – aqui propriamente o teste é realizado. Observe o comando que a princípio parece bastante complicado:
    • this.mockMvc.perform(get(“/”)).andDo(print()).andExpect(status().isOk())
      .andExpect(content().string(containsString(“eu não acredito”)));
    • Na verdade ele esta fazendo o seguinte, traduzindo os métodos para o português:
      • nestaclasse.objetoquesimulaumcliente.façaumachamada(metodoGet(“/”).efaça(imprimaotextoretornado()).eesperepor(status().isOk()).eesperepor(conteudo().emformadetexto(contenha(“eu não acredito”)));
package br.com.faltoupontoevirgula.projetospring;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import br.com.faltoupontoevirgula.projetospring.controller.HomeController;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ProjetoSpringApplicationTests {

@Autowired
private HomeController controller;
@Autowired
private MockMvc mockMvc;

@Test
public void contextLoads() {
//Verifica a existência da instância do controlador
assertThat(controller).isNotNull();
}

@Test
public void homeControllerTest() throws Exception {
//Teste do método index
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("eu não acredito")));
}

}

Primeira interface

Agora que já entendemos como funcionam os controladores, e como testar o seu resultado, vamos criar nossa primeira interface gráfica. Para isso vamos inicialmente utilizar a biblioteca Thymeleaf (https://www.thymeleaf.org), que se trata de uma engine para controlar templates do lado servidor. Lembre-se que quando criamos nosso projeto Spring, incluímos o Thymeleaf como uma das dependências. O Thymeleaf, inclui ao HTML algumas tags XML que simplificam o desenvolvimento da aplicação.

Nosso objetivo agora é construir uma tela que liste dados de pacientes. Como primeiro passo precisamos configurar o Thymeleaf, incluindo no final do arquivo application.properties as seguintes linhas:

  • spring.thymeleaf.check-template-location força o Spring a verificar a existência da pasta contendo os arquivos de layout;
  • spring.thymeleaf.prefix – indica o caminho base do template;
  • spring.thymeleaf.suffix – indica a extensão dos arquivos que serão considerados como templates.
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/dbprojetospring
spring.datasource.username=bob
spring.datasource.password=bob

spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

Para melhor organizar nosso código, vamos criar um conjunto de pastas para separar cada interface HTML para seu respectivo controlador. Então dentro da pasta src/main/resources/templates, clique com o botão direito do mouse new ->Folder.

Na próxima tela, informe o nome paciente e confirme no botão Finish.

Agora vamos criar o primeiro arquivo HTML que será a interface inicial da tela de pacientes, conforme a imagem abaixo, clique com o botão direito sobre a pasta recém criada paciente com o botão direito do mouse, new -> Other…

Na próxima tela, procure pela pasta Web, e dentro dela selecione o tipo HTML File, confirme no botão Next.

Em seguida na última etapa desses processo informe o nome do arquivo como index.html, e confirme no botão Finish.

O código a seguir, é o início da nossa interface, ainda utilizando apenas tags HTML criamos uma simples tabela.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head>
		<title>Insert title here</title>
	</head>
	<body>
		<table>
			<thead> 
				<tr>
					<td>Nome</td>
					<td>Sexo</td>
				</tr>
			</thead>
			<tbody>
				<td>Zezinho</td>
				<td>Masculino</td>
			</tbody>
		</table>
	</body>
</html> 

Para permitir que essa interface seja visualizada, precisamos criar um controlador que redirecione a chamada para o arquivo HTML. Para isso clique com o botão direito do mouse sobre o pacote br.com.faltoupontoevirgula.projetospring.controller e crie uma nova classe Java com o nome PacienteController. O código desta classe deve ser semelhante ao exemplo abaixo:

  • Observe que na linha 9 inserimos uma nova anotação @RequestMapping(“/paciente”), com essa nova anotação o Spring compreende que qualquer URL com o endereço http://localhost:8080/paciente deverá ser direcionada para este controlador
  • Nas linhas 12 à 16 existe o método que responde à chamada da URL principal, observer que a anotação @GetMapping(“”) recebe um parâmetro como string vazia, assim todas as chamadas para a URL http://localhost:8080/paciente/ serão redirecionadas para ela. Observe que o retorno do método foi modificado para a classe ModelAndView que permite com que o controlador retorne como resposta para o cliente uma view e o model com os dados. Por fim na linha 15 retornamos uma instância da classe ModelAndView passando como parâmetro para o construtor o caminho relativo para nosso arquivo HTML “paciente/index”, não sendo necessário informar a extensão .html do arquivo.
package br.com.faltoupontoevirgula.projetospring.controller;

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;

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@GetMapping("")
public ModelAndView index() {

return new ModelAndView("paciente/index");
}
}

Ao acessar a URL pelo navegador o resultado será conforme a imagem a seguir:

Vamos escrever um teste unitário para garantir que esta alteração esteja coberta pelos testes unitários. Para isso altere o código da classe ProjetoSpringApplicationTests conforme exemplo abaixo:

  • na linha 9 incluímos um novo import static para função xpath que será explicada a seguir;
  • nas linhas 29 e 30 incluímos um novo atributo do tipo PacienteController e utilizamos a anotação @Autowired para instanciar automaticamente o objeto;
  • na linha 38 realizamos o testes se o controlador pode ser instanciado;
  • e por fim nas linhas 48 até 54 implementamos um novo teste, que faz as seguintes verificações:
    • realiza a invocação da página /paciente
    • verifica se o retorno é Status OK
    • utiliza a função xpath para ler o HTML retornado e checar se existe uma tabela dentro do código;
    • utiliza a função xpath para ler o HTML retornado e checar se existe uma tag TD cujo conteúdo seja Zezinho.
package br.com.faltoupontoevirgula.projetospring;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import br.com.faltoupontoevirgula.projetospring.controller.HomeController;
import br.com.faltoupontoevirgula.projetospring.controller.PacienteController;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ProjetoSpringApplicationTests {

@Autowired
private HomeController controller;
@Autowired
private PacienteController pacienteController;
@Autowired
private MockMvc mockMvc;

@Test
public void contextLoads() {
//Verifica a existência da instância do controlador
assertThat(controller).isNotNull();
assertThat(pacienteController).isNotNull();
}

@Test
public void homeControllerTest() throws Exception {
//Teste do método index
this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
.andExpect(content().string(containsString("eu não acredito")));
}

@Test
public void pacienteControllerTest() throws Exception {
//Teste do método index
this.mockMvc.perform(get("/paciente")).andDo(print()).andExpect(status().isOk())
.andExpect(xpath("//table").exists())
.andExpect(xpath("//td[contains(., 'Zezinho')]").exists());
}

}

O conteúdo apresentado nesta tela ainda esta estático, agora vamos transformá-lo em dinâmico. Para isso vamos introduzir o conceito de classe model. Essas classes são nossas entidades, que foram encontradas durante o processo de levantamento e análise dos requisitos. As classes model são POJOs (Plain old Java objects) o que significa que são classes Java com atributos e métodos GET/SET. Para criar nossa classe paciente, vamos primeiro criar uma estrutura de pacotes para organizar esse tipo diferente de classe. Clique com o botão direito do mouse sobre o pacote br.com.faltoupontoevirgula.projetospring -> new -> package

Na próxima tela, informe o nome do pacote .model no fim do campo nome, mantendo a base do pacote e confirme no botão Finish.

Dentro deste novo pacote, clique com o botão direito do mouse e selecione a opção new ->Class, crie uma classe com o nome Paciente. O código dela deve ser semelhante ao exemplo abaixo:

package br.com.faltoupontoevirgula.projetospring.model;

public class Paciente {
private long id;
private String nome;
private String sexo;

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 que temos uma classe que representa os dados podemos refatorar o código do controlador PacienteController para que o método index(), simule a geração dinâmica dos dados, e passe para a view desenhar a tela. As alterações feitas foram as seguintes:

  • Linha 3 e 4 – foram importadas a interface List e a classe ArrayList;
  • Linha 20 – foi criada um atributo listaPacientes do tipo lista e uma instância da classe ArrayList foi gravada neste atributo (variável);
  • Linhas 22,23 e 24 – uma nova instância da classe Paciente foi criada e armazenada no atributo p1, a partir da instância armazenada neste atributo os métodos setNome() e setSexo() foram chamados passando os valores para dentro do objeto;
  • Linha 26 – A instância da classe Paciente, cuja referência esta na variável p1, foi adicionada para dentro do ArrayList contido no atributo listaPacientes;
  • por fim na linha 28, a passagem de parâmetro para o construtor da classe ModelAndView foi modificado, além do caminho relativo para o arquivo da view, mais dois parâmetros foram incluídos, o segundo é uma string “listapac” que será o nome do atributo model que será utilizado na view para recuperar os dados enviados para ela, e o terceiro parâmetro é a lista de pacientes que será enviada para a view.

package br.com.faltoupontoevirgula.projetospring.controller;

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

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.Paciente;


@Controller
@RequestMapping("/paciente")
public class PacienteController {

    @GetMapping("")
    public ModelAndView index() {
        List listaPaciente = new ArrayList();
       
        Paciente p1 = new Paciente();
        p1.setNome("Zezinho");
        p1.setSexo("Masculino");
       
        listaPaciente.add(p1);
       
        return new ModelAndView("paciente/index","listapac",listaPaciente);
    }
}

Para concluir, devemos alterar o código da nossa view (arquivo templates/paciente/index.html) para incluir as tags do Thymelead que permitirão carregar os dados diretamente enviados pelo objeto model através do controlador.
Observe que a tag HTML recebeu dois namespaces o th e o layout. A tag tr responsável por criar as linhas da tabela, recebeu uma nova propriedade que é o th:each e a notação se assemelha ao foreach da linguagem Java, para cada item na listapac a instância do objeto será gravada no atributo umpac. As tags td receberam a propriedade th:text onde da instância do objeto que esta no atributo umpac o valor das propriedades nome e sexo é carregado.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head>
		<title>Insert title here</title>
	</head>
	<body>
		<table>
			<thead> 
				<tr>
					<td>Nome</td>
					<td>Sexo</td>
				</tr>
			</thead>
			<tbody>
				<tr th:each="umpac : ${listapac}">
					<td th:text="${umpac.nome}"></td>
					<td th:text="${umpac.sexo}"></td>
				</tr>
			</tbody>
		</table>
	</body>
</html>

Caso você recarregue o servidor e a página http://localhost:8080/paciente irá observar que visualmente o resultado é o mesmo, mas agora não temos mais um conteúdo estático, ele esta sendo gerado pelo controlador.

Mapeamento objeto relacional

O processo de mapeamento objeto relacional é realizado entre os objetos model (entidades) e o banco de dados relacional, uma vez que esse processo é necessário pois nem todas as regras de associação entre objetos são suportadas por bancos de dados relacionais. Um exemplo clássico são as associações muitos para muitos, que não podem ser realizadas diretamente no banco de dados por quebrarem as regras formais de modelagem do banco de dados. Portanto podemos compreender o mapeamento objeto relacional como um mecanismo que transforma objetos em estruturas do banco de dados, geralmente uma ou mais tabelas. Esse processo no Spring Framework é suportado pela Java Persistence API (JPA), apoiado pelo provedor de persistência Hibernate. Podemos entender que o JPA é um conjunto de classes representadas por anotações que são inseridas nas classes de modelo, e que são entendidas pelo hibernate que possui a capacidade de interagir com diversos bancos de dados, realizado a criação – alteração e comunicação com eles. Para iniciar o processo vamos alterar nossa classe de modelo Paciente, conforme o exemplo de código abaixo:

  • Linhas 3 à 8 – imports das anotações necessárias
  • Linha 10 – a anotação @Entity indica para o JPA que esta classe deverá ser representada no banco de dados como uma estrutura de tabela
  • Linhas 12 e 13 – a anotação @Id indica que o atributo logo abaixo dela, será utilizado como chave primária e única da tabela, a anotação @GeneratedValue indica como será a estratégia de geração do valor desse campo, no caso um contador sequencia gerenciado pelo mecanismo do banco de dados
  • Linhas 15 e 16 – a anotação @NotNull impede que o atributo seja salvo no banco de dados com valor nulo, e a anotação @Length define o tamanho máximo e mínimo do campo, além de uma mensagem de validação que será usada na construção dos formulários
package br.com.faltoupontoevirgula.projetospring.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

@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;

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;
}
}

Após concluir estas alterações, republique o código no servidor Spring e consulte o banco de dados a tabela paciente terá sido automaticamente criada no banco de dados.

Neste ponto do processo você já possui uma classe onde o mapeamento objeto relacional foi feito, a classe Paciente. Caso siga para a próxima seção, você irá construir uma tela de cadastro simples para esta classe. Porém você pode optar por primeiro construir todo o mapeamento objeto relacional das classes do pacote model, ficando ao seu cargo essa decisão, mas o mapeamento precisa ser feito para que o sistema possa ser desenvolvido.
Toda aplicação orientada a objetos tem o desafio de como persistir os dados armazenados nos objetos entity, sendo que, a solução de utilizar Sistemas Gerenciadores de Bancos de Dados relacionais ainda é a solução mais adotada pelas empresas devido o custo e principalmente pelo domínio da tecnologia de armazenamento de dados. Porém, os objetos não podem ser simplesmente armazenados em tabelas de bancos relacionais principalmente porque o paradigma orientado a objetos aceita que duas classes tenham associações muitos para muitos, já o modelo da banco de dados relacional não permite essas associações devido a quebra das regras das formas normais. Para solucionar esse problema utiliza-se uma camada de tradução entre o paradigma OO e o relacional conhecida como camada de persistência que normalmente adota o padrão de mapeamento objeto relacional. Essa camada pode ser construída manualmente, onde o programador fica responsável por ler a estrutura dos objetos de entidade e converter as modificações em seus atributos para instruções SQL que sincronizem o estado no banco de dados. Mas para simplificar o desenvolvimento este material irá utilizar a Java Persistence API (JPA) que foi implementada a partir da versão 3.0 da plataforma de desenvolvimento Enterprise Java Beans e que pode também ser utilizada fora do contexto de um servidor de aplicação, em uma plataforma JSE. Uma das grandes evoluções da JPA a partir da versão 3 do EJB, foi a adoção do conceito de ANOTAÇÕES para definir como o mapeamento objeto relacional deve ser feito. Para simplificar o entendimento de como realizar o mapeamento objeto relacional, vamos utilizar um exemplo de diagrama de domínio e então construir as classes e realizar o mapeamento passo a passo. O diagrama a seguir ilustra as classes que serão consideradas neste exemplo.

Com base neste diagrama vamos iniciar a criação das demais classes, além do Paciente. Vamos iniciar pela segunda classe mais simples que é o Procedimento, ao observar o diagrama, esta classe não tem nenhuma associação de dependência com outras classes. Por isso seu código fica da seguinte maneira:

package br.com.faltoupontoevirgula.projetospring.model;

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

@Entity
public class Procedimento {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(length=10000)
private String descricao;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getDescricao() {
return descricao;
}
public void setDescricao(String descricao) {
this.descricao = descricao;
}

}

IMPORTANTE: NÃO ESQUEÇA DE GERAR OS MÉTODOS GET/SET
Observe que utilizamos as anotações básicas @Entity para indicar que esta classe deverá ser persistida pelo Hibernate, além do @Id e da anotação @GeneratedValue. A nova anotação utilizada neste código é a @Column que possui diversos parâmetros, um deles é o length que permite informar o tamanho máximo de informação que será armazenado neste campo, sendo que isso depende do tipo do dado utilizado. Ao reiniciar o projeto podemos observar no banco de dados que o campo descrição foi criado com um tipo varchar de 10000 caracteres.

@ManytoMany

A próxima classe que vamos construir, é a classe Médico. Ao observar o diagrama de classes entity, notamos que existe uma associação entre esta classe e o Procedimento, que representa a lista de procedimentos que um médico está apto a realizada. A forma correta de ler esta associação é: Um médico pode realizar diversos Procedimento, e um Procedimento pode estar ligado a diversos médicos. Neste caso surge a associação Muitos para Muitos. Esse tipo de associação é muito comum em sistemas orientados a objetos, mas totalmente inviável para banco de dados relacionais. Vamos primeiro ver a implementação no código fonte.

package br.com.faltoupontoevirgula.projetospring.model;

import java.util.ArrayList;
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.ManyToMany;

@Entity
public class Medico {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(length=500)
private String nome;
private String CRM;

@ManyToMany(cascade= {CascadeType.MERGE,CascadeType.REFRESH})
@JoinColumn(name="procedimento_id")
List listaProcedAutorizados = new ArrayList();

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 getCRM() {
return CRM;
}
public void setCRM(String cRM) {
this.CRM = cRM;
}
}

Além das anotações que já estamos acostumados, surge pela primeira vez a anotação @ManyToMany, muitos para muitos. Observe que ela esta sobre um atributo do tipo List (java.util.List) utilizando a notação diamante para a classe Procedimento, esse atributo recebe uma instância da classe concreta ArrayList. Portanto, a lista foi implementada nesta classe porque o diagrama mostra que a navegabilidade acontece da classe Médico para a classe Procedimento, lista permite que uma instância da classe Médico receba diversas instâncias da classe Procedimento. Por fim a anotação @ManyToMany recebe a configuração do atributo cascade. O Cascade define a propagação das ações de uma classe base, para suas classes associadas filhas, não confunda com o conceito de herança. Neste caso, criou-se um array contendo duas diretivas de cascade, a primeira CascadeType.MERGE significa que toda vez que a classe médico sofrer um UPDATE no banco de dados, a lista de objetos da classe Procedimento também terá seus dados atualizados. A diretiva CascadeType.REFRESH significa que toda vez que os dados da classe Médico forem consultados no banco de dados, os dados dos procedimentos ligados a ele também serão recuperados do banco em sua versão mais atual.

Observe no banco de dados, pela figura que além da tabela médico, foi criada uma tabela medico_lista_proced_autorizados, que tem dois campos chave um da tabela médico e outro da tabela procedimento.

@ManytoOne e @OneToMany

A próxima classe que vamos criar, é a classe Consulta, pois nela vamos encontrar diversos tipos importantes de relacionamentos com outras classes, utilizando outros dois tipos importantes de multiplicidade: um para muito e muitos para um.

O código a seguir apresenta a classe consulta parcialmente mapeada, além das anotações que já utilizamos, podemos observar na linha 21 que existe um atributo do tipo Date, sobre ele devemos colocar a anotação @Temporal, para indicar para o Hibernate, que não se trata de um atributo objeto, mas sim de um valor de data. Configurando o valor desta anotação para TemporalType.TIMESTAMP, o campo no banco de dados será criado com o tipo datetime que representa data e hora.

Nas linhas 25 e 28, observamos a implementação de duas associações do tipo Muitos para Um. Pois ao ler o diagrama de entidades, observamos que a classe Consulta tem uma associação com a classe Paciente e com a classe Médico, na direção muitas consultas possuem um Paciente, e um paciente pode ter várias consultas, e muitas Consultas possuem um Médico e um Médico possui muitas consultas. Como a navegabilidade parte da Consulta para as demais classes, criamos um atributo simples de cada tipo de classe e anotamos com @ManyToOne, lembrando do cascade.

package br.com.faltoupontoevirgula.projetospring.model;

import java.util.Date;

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.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Consulta {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@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;

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;
}
}

Ao reiniciar o projeto Spring, observe que no banco de dados foi criada a tabela Consulta, contendo dois campos de chave estrangeira: medico_responsavel_id que contém o valor da chave primária da tabela Médico associada, e paciente_id que contém o valor da chave primária da tabela Paciente.

Para concluir o mapeamento, vamos criar a classe ProcedimentoRealizado, essa classe associativa é utilizada para registrar quais foram os procedimentos realizados por um médico durante a consulta. Seu código fica da seguinte forma, colocamos as anotações básicas para que a classe seja considerada uma entidade, e nas linhas 19 e 20 criamos um atributo simples do tipo Procedimento e sobre ele a anotação @ManyToOne, conforme diagrama e exemplos anteriores.

package br.com.faltoupontoevirgula.projetospring.model;

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.ManyToOne;

@Entity
public class ProcedimentoRealizado {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@Column(length=10000)
private String descricao;
private float valor;
@ManyToOne(cascade= {CascadeType.REFRESH,CascadeType.MERGE})
private Procedimento procedimento;

public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getDescricao() {
return descricao;
}
public void setDescricao(String descricao) {
this.descricao = descricao;
}
public float getValor() {
return valor;
}
public void setValor(float valor) {
this.valor = valor;
}
public Procedimento getProcedimento() {
return procedimento;
}
public void setProcedimento(Procedimento procedimento) {
this.procedimento = procedimento;
}
}

Agora basta alterar novamente a classe Consulta, para que na 19 seja criado um atributo do tipo List a notação diamante para a classe genérica ProcedimentoRealizado, cujo nome da variável é listaProcedimentos. Esse atributo recebe uma instância da classe ArrayList, e também duas anotações @OneToMany que neste caso significa uma consulta para muitos ProcedimentosRealizados, e a anotação @JoinColumn para indicar como será o nome da coluna que representará a chave estrangeira na tabela ProcedimentoRealizado. IMPORTANTE: NÃO ESQUEÇA DE GERAR OS METODOS GET/SET PARA ESSE NOVO ATRIBUTO.

@Entity
public class Consulta {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
@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)
@JoinColumn(name="consulta_id")
private List listaProcedimentos = new ArrayList();

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 getListaProcedimentos() {
return listaProcedimentos;
}

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

O resultado no banco de dados e que a tabela procedimento_realizado recebe um novo campo chamado consulta_id que é a chave estrangeira que associa as duas tabelas.

Iniciando a construção de um CRUD do Paciente

Agora que todas as nossas classes model já foram mapeadas, podemos dar início ao processo de construção da primeira tela de CRUD (Create-Read-Update-Delete). Já demos início a este processo criando a primeira view index.html para o controlador PacienteController, mas vamos deixar a aparência da aplicação melhor. Para isso, vamos incluir o Bootstrap como dependência da nossa aplicação, sendo que o Bootstrap é um conjunto de arquivos CSS e JavaScript que simplificam o desenvolvimento de interfaces HTML.

Para isso, em seu projeto Spring, clique duas vezes no arquivo pom.xml, o Eclipse ira abrir a interface de edição do arquivo, clique na aba Dependencies e clique no botão Add.

No campo de busca digite org.webjars, abra a pasta do bootstrap e selecione a maior versão disponível, confirmando no botão OK.

Repita o mesmo processo para adicionar a biblioteca jquery, que é uma dependência do bootstrap. Clique no botão Add, na caixa de pesquisa digite org.webjars, selecione jquery e a versão mais atual. Confirme no botão OK.

Para concluir feche o arquivo pom.xml, automaticamente o projeto será atualizado incluindo as bibliotecas do Bootstrap.

Bootstrap e Thymeleaf

Agora que o projeto esta configurado podemos iniciar a construção de nossas interfaces utilizando a biblioteca Bootstrap, é importante visitar o site para conhecer melhor os componentes: https://getbootstrap.com/docs/4.0/getting-started/introduction/

Conforme o manual de inicio do Bootstrap, todas as nossas telas HTML precisam ter um tag LINK apontado para o caminho e arquivo bootstrap.css, e pelo meno dois tags SCRIPT para os arquivos jquery.min.js e bootstrap.min.js. Para não necessitar incluir essas tags em todos os arquivos HTML, vamos criar um template padrão que será importado em cada interface que for desenvolvida.

Para isso dentro da pasta templates, crie um arquivo chamado layout.html, dentro deste arquivo cole o conteúdo abaixo:

<!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}" />
	</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>
		</div>
	</body>
</html>

Neste código utilizamos o mecanismo de fragmentos do thymeleaf que possibilita com que trechos de código HTML sejam nomeados e inseridos em outras páginas HTML. Criamos três fragmentos importantes

  • Fragmento htmlhead – responsável por substituir o tag HEAD de todas as páginas, dentro desse tag inserimos o link, apontando para o caminho relativo da biblioteca webjars bootstrap que foi inserida como dependência do projeto
  • Fragmento menu – responsável por criar a estrutura de tags NAV necessária para criar o menu no topo da página da aplicação
  • Fragmento footer – responsável por criar o rodapé da nossa página HTML, basicamente esse código está incluindo as duas tags Script necessárias para que o código do jquery e do bootstrap sejam inseridos em todas as páginas.

Agora vamos alterar o código do nosso arquivo templates/paciente/index.html, observe que substituímos toda a tag HEAD pelo codigo na linha 3, a diretiva th:include substituirá o código pelo existente no template. Na linha 5, inserimos uma tag div com a classe container necessária para que o bootstrap funcione corretamente. Logo abaixo na linha 6, inserimos o tag NAV com a diretiva th:replace que irá substituir o código pelo menu definido no arquivo layout.html. Por fim na linha 21 fechamos a nova tag div, e incluímos na linha 22 uma nova tag div com a diretiva th:include que ira incluir as tags script necessárias para importar os códigos javascript do bootstrap e do jquery.

<div class="container">
<nav>(menu)</nav>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Sexo</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div id="footer">(footer)</div>

Como próximo passo vamos utilizar a classe Tables do Bootstrap (mais informações em: https://getbootstrap.com/docs/4.0/content/tables/) para deixar nossa lista de pacientes mais agradável. Na linha 7 inserimos uma nova tag DIV utilizando as classes border border-dark para criar uma caixa onde iremos colocar nossa tabela. Na linha 8 alteramos a tag TABLE inserindo a classe table, para que o bootstrap possa aplicar a folha de estilo, na linha 8 incluímos a classe thead-light na tag thead, para destacar o cabeçalho. Nas linhas 11 e 12 inserimos o atributo scope que tem como objetivo agrupar as tags TH como colunas. Por fim na linha 16 alteramos a tag TR para incluir o scope ROW.

<div class="container">
<nav>(menu)</nav>
<div class="border border-dark">
<table class="table">
<thead class="thead-light">
<tr>
<th scope="col">Nome</th>
<th scope="col">Sexo</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="footer">(footer)</div>

Repository – Acessando banco de dados

Agora que nossa interface esta pronta, e bem mais atrativa, precisamos finalmente fazer o acesso ao banco de dados para recuperar as informações do Paciente. Para isso vamos utilizar a API do JPA juntamente com a API de acesso a dados do Spring (mais informações aqui:https://spring.io/guides/gs/accessing-data-jpa/).

O acesso de dados utilizando a API JPA no Spring é feito através de um serviço chamado Repository, basicamente e uma INTERFACE que da acesso aos objetos da API JPA de uma forma simplificada, para melhor entender seu funcionamento vamos criar o código necessário, que é bastante simples.

Primeiro vamos criar um novo pacote chamado repository, clique com o botão direito sobre o pacote principal da aplicação e vá no menu new -> package. Na próxima tela, informe no campo name o final do pacote .repository e confirme no botão Finish.

Uma vez que o pacote foi criado, clique com o botão direito sobre o pacote repository e acione a opção new -> interface.

Na próxima tela, informe o nome da interface como PacienteRepository e confirme no botão finish.

O código desta interface fica inicialmente como o exemplo a seguir, basicamente nós devemos anotar a interface com o @Repository, e em seguida estender outra interface chamada JpaRepository, durante esse processo, utilizamos a notação diamante para passar a classe Paciente como um parâmetro genérico, e em seguida o tipo do dado que foi utilizando no mapeamento objeto relacional com a anotação @Id para identificar o atributo que é a chave primária. Importante destacar, que apesar de que no objeto foi utilizado o tipo primitivo long, aqui precisamos utilizar sua classe Warpper Long, pois tipos primitivos não podem ser passados como parâmetro em Java.

package br.com.faltoupontoevirgula.projetospring.repository;

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

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

@Repository
public interface PacienteRepository extends JpaRepository&lt;paciente, long=""&gt;{&lt;/paciente,&gt;

}

Este código causa bastante estranheza a primeira vista, porque ele não possui nenhum método e utiliza apenas interfaces. Quando ele for invocado dentro do controller, será automaticamente instanciado com classes capazes de acessar o banco de dados.

Agora que o repositório esta pronto, podemos alterar nossa classe PacienteController para invocar o PacienteRepository. Primeiro na linha 18 e 19, criamos um atributo do tipo PacienteRepository e anotamos com @Autowired, anotação que já utilizamos nos testes unitários. Agora observe que muito do código do método index() foi removido, basicamente na linha 23, chamamos o método findAll() através da referencia que está no atributo pacienteRepository, e ele fica responsável por executar a consulta no banco de dados na tabela Paciente, receber o resultado da consulta SQL, transformar as tuplas recebidas do banco de dados em uma coleção de objetos Paciente. E a linha 25 permanece como estava, retornando a lista de clientes para a view.

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.Paciente;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

@GetMapping("")
public ModelAndView index() {
List listaPaciente = this.pacienteRepository.findAll();

return new ModelAndView("paciente/index","listapac",listaPaciente);
}
}

Após concluir essas alterações e salvar os fontes, basta acessar o banco de dados mysql e executar alguns comandos Insert na tabela paciente conforme exemplo abaixo.

Ao atualizar a interface no navegador, o resultado será que os dados inseridos no banco de dados serão apresentados na view da aplicação.

Formulário de inclusão e alteração

Agora que já estamos com a consulta básica criada, vamos continuar o desenvolvimento para incluir o formulário que será utilizado no processo de inclusão de um novo registro e alteração dos registros existentes. Para isso, vamos primeiro alterar nossa tela index.html para incluir os botões: novo no topo da tabela, e em cada linha da tabela os botões alterar e excluir.

<div class="container">
<nav>(menu)</nav><a class="btn btn-primary btn-lg" role="button" href="#">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"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td><a class="btn btn-secondary btn-lg" role="button" href="#">Alterar</a>
<a class="btn btn-warning btn-lg" role="button" href="#">Excluir</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="footer">(footer)</div>

O resultado esperado é o apresentado na imagem abaixo.

Agora vamos criar um novo arquivo chamado form.html dentro da pasta resources -> templates -> paciente, sendo esse código a base para todas as telas desenvolvidas no sistema.

<div class="container">
<nav>(menu)</nav>
<div class="border border-dark"></div>
</div>
<div id="footer">(footer)</div>

O próximo passo é dentro da DIV mais interna, inserir o código necessário para desenhar nosso formulário, conforme o exemplo abaixo. Na linha 8 inserimos a TAG form, para iniciar nosso formulário, em seguida o primeiro campo do formulário é criado com a TAG input,mas como seu tipo e Hidden (escondido) esse campo não será visível para o usuário. Em seguida criamos uma div, devido o Bootstrap para agrupar o label Nome e o campo de texto Input do tipo text cujo id é txtnome. Abaixo criamos o segundo campo, também dentro de uma div, mas utilizando as tags Label para o sexo e o tag Select/Option. Esse tag desenha no formulário um seletor de opções, descritas pelas tags option. Por fim todo formulário precisa de um botão enviar, criado pelo tag button com o tipo submit.

<div class="container">
<nav>(menu)</nav>
<div class="border border-dark">
<form><input id="txtid" type="hidden" />
<div class="form-group"><label for="txtnome">Nome</label>
<input id="txtnome" class="form-control" type="text" placeholder="Nome" /></div>
<div class="form-group"><label for="slcsexo">Sexo</label>
<select id="slcsexo" class="form-control">
<option>Masculino</option>
<option>Feminino</option>
<option>Outro</option>
</select></div>
<button class="btn btn-primary" type="submit">Enviar</button>

</form></div>
</div>
<div id="footer">(footer)</div>

Para que o formulário possa ser visualizado, precisamos alterar nossa classe PacienteController, criando um novo método que será responsável por carregar esta view form.html. Para isso altere o código da classe inserindo as seguintes linhas abaixo. Criamos um método chamado createForm anotando com @GetMapping(“/novo”) o que significa que quando o usuário digitar http://localhost:8080/paciente/novo este método será chamado. Esse método recebe por parâmetro uma instância da classe Paciente, que será utilizada para associar com os campos no formulário form.html. Por fim esse método retorna apenas o caminho no formato de texto para nosso arquivo view que é “paciente/form”, essa é outra opção de retorno dos métodos de um controlador quando não se deseja enviar dados para a view, apenas carregar o html.

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.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

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

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

@GetMapping("")
public ModelAndView index() {
List listaPaciente = this.pacienteRepository.findAll();

return new ModelAndView("paciente/index","listapac",listaPaciente);
}
@GetMapping("/novo")
public String createForm(@ModelAttribute Paciente paciente) {
return "paciente/form";
}
}

Agora vamos complementar nossa view form.html, incluindo diversos atributos do Thymeleaf que irão vincular os campos da tela, aos atributos da instância da classe Paciente, que foi recebida por parâmetro no método createForm, que escrevemos no passo anterior. O código do arquivo form.html sofreu as seguintes alterações conforme exemplo abaixo:

  • Linha 8 – na tag FORM foi inseridos os atributos th:action=”@{/paciente/(form)}” th:object=”${paciente}” action=”#” method=”post” O th:action indica para o Thymeleaf qual rota deve ser chamada para enviar os dados do formulário, o th:object indica qual o nome do atributo que será utilizado como base para referenciar o objeto contendo os dados que serão submetidos pelo formulário ao controlador, a tag action que é do HTML puro indica o caminho de destino para onde os dados do formulário deveriam ser enviados mas como o Thymeleaf já cuida disso com a tag th:action, devemos colocar o simbolo #, e por fim o atributo method indica que será utilizado o verbo POST do protocolo HTTP para o envio dos dados para o servidor.
  • Na linha 9 inserimos o atributo th:field=”*{id}” que conecta esse campo da view com o atributo id da classe paciente, esse processo é chamado de Binding, e irá ocorrer em todos os campos do formulário
  • Na linha 12 inserimos o atributo th:field=”*{nome}” no tag input para vincular o campo ao atributo nome da classe paciente
  • Na linha 16 inserimos o atributo th:field=”*{sexo}” no tag select para vincular o campo ao atributo sexo da classe paciente
  • Nas linhas 17 à 19, incluimos o atributo th:value em cada uma das opções para informar ao Thymeleaf qual valor deverá ser atribuído ao atributo sexo, dependendo do item selecionado
<div class="container">
<nav>(menu)</nav>
<div class="border border-dark">
<form action="#" method="post"><input id="txtid" type="hidden" />
<div class="form-group"><label for="txtnome">Nome</label>
<input id="txtnome" class="form-control" type="text" placeholder="Nome" /></div>
<div class="form-group"><label for="slcsexo">Sexo</label>
<select id="slcsexo" class="form-control">
<option>Masculino</option>
<option>Feminino</option>
<option>Outro</option>
</select></div>
<button class="btn btn-primary" type="submit">Enviar</button>

</form></div>
</div>
<div id="footer">(footer)</div>

Com o formulário pronto e vinculado a instância da classe Paciente, falta alterar novamente nossa classe PacienteController para criar um novo método save, esse método será responsável por receber o POST do formulário, por esse motivo recebe a anotação @PostMapping(params=”form”), então ele irá utilizar o método save do PacienteRepository para salvar no banco de dados os dados, e por fim redirecionar o usuário para a tela inicial.

package br.com.faltoupontoevirgula.projetospring.controller;

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.Paciente;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

@GetMapping("")
public ModelAndView index() {
List listaPaciente = this.pacienteRepository.findAll();

return new ModelAndView("paciente/index","listapac",listaPaciente);
}
@GetMapping("/novo")
public String createForm(@ModelAttribute Paciente paciente) {
return "paciente/form";
}

@PostMapping(params="form")
public ModelAndView save(@Valid Paciente paciente, BindingResult result, RedirectAttributes redirect) {

paciente = this.pacienteRepository.save(paciente);

return new ModelAndView("redirect:/paciente");
}
}

Para concluir basta alterar nosso arquivo index.html para fazer com que o botão novo chame a view form.html através da rota /paciente/novo, conforme exemplo abaixo:

<div class="container">
<nav>(menu)</nav><a class="btn btn-primary btn-lg" role="button" href="form.html">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"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td><a class="btn btn-secondary btn-lg" role="button" href="#">Alterar</a>
<a class="btn btn-warning btn-lg" role="button" href="#">Excluir</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="footer">(footer)</div>

Agora é importante verificar se a aplicação esta funcionando, acesse http://localhost:8080/paciente e tente realizar a inclusão de um novo paciente.

Alteração dos registros

Como concluímos a inclusão de novos registros, podemos trabalhar no processo de alteração dos registros existentes, na realidade ele esta praticamente pronto. Pois já construímos um formulário que esta vinculado ao objeto Paciente, e esse formulário ao receber dados esta preparado para enviar os dados digitados para o método save do controlador, que tem como responsabilidade tanto incluir como atualizar dados dos pacientes.

O que falta ser feito agora é alterar o código do botão alterar que é gerado dentro da tabela que lista os registros para chamar um método do controlador que será responsável por buscar os dados da instância do objeto que se deseja alterar, e carregar a view form.html com esses dados.

Conforme o código de exemplo a seguir, a linha 22 que continha o link, apresentado no formato de botão, para a ação de alterar, recebeu o atributo href apontando para a view form.html, e o novo atributo th:href=”@{‘/paciente/alterar/’ + ${umpac.id}}”. Desta forma ao clicar no botão alterar, o navegador será direcionado para a rota http://localhost:8080/paciente/alterar/. Com isso é possível identificar qual registro o usuário deseja alterar.

<div class="container">
<nav>(menu)</nav><a class="btn btn-primary btn-lg" role="button" href="form.html">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"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td><a class="btn btn-secondary btn-lg" role="button" href="form.html">Alterar</a>
<a class="btn btn-warning btn-lg" role="button" href="#">Excluir</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="footer">(footer)</div>

Para concluir a funcionalidade de alterar basta modificar o código do controlador PacienteController para incluir o método alterarForm, conforme exemplo a seguir. Esse método recebe a anotação @GetMapping(value=”/alterar/{id}”) que referencia que o verbo GET do protocolo HTTP será utilizado, e que a rota vinculada ao método deve conter /alterar/{id} onde {id} é um valor que será automaticamente salvo nesta variável. Na passagem de parâmetro do método observe que existe a anotação seguida da definição do parâmetro de entrada do tipo objeto Paciente @PathVariable(“id”) Paciente paciente Com isso o Spring Data já compreende que ele esta recebendo o valor da chave primária da tabela Paciente, e que precisa realizar uma busca utilizando o repositório do objeto modelo Paciente, para encontrar a instância referente a este atributo ID. E na linha seguinte, retornamos a instância do objeto para a view form.html.

package br.com.faltoupontoevirgula.projetospring.controller;

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.Paciente;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

@GetMapping("")
public ModelAndView index() {
List listaPaciente = this.pacienteRepository.findAll();

return new ModelAndView("paciente/index","listapac",listaPaciente);
}
@GetMapping("/novo")
public String createForm(@ModelAttribute Paciente paciente) {
return "paciente/form";
}

@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) {
return new ModelAndView("paciente/form","paciente",paciente);
}
}

Acesse http://localhost:8080/paciente e tente realizar alteração de um registro já cadastrado.

Exclusão dos registros

Por último mas não menos importante, vamos construir a funcionalidade de exclusão dos registros do cadastro. Para isso, o botão excluir que até o momento não tinha nenhuma funcionalidade, irá a partir do código abaixo, executar um script JQuery para abrir uma janela modal de confirmação de exclusão e atualizar dentro do código desta janela o id do registro que se deseja excluir. Caso o usuário selecione a opção confirmar, o sistema irá chamar um método remover no controlador para realizar a exclusão do registro no banco de dados.

As alterações no código são mais extensas na view index.html, conforme descrito e apresentado no código a seguir.

  • Linha 23 – alteramos o código do botão excluir, para que ele chame a tela modal de confirmação além de passar o valor da chave primária ID do paciente que se deseja excluir para a tela de confirmação pelo atributo th:data-id=”${umpac.id}”
  • Linhas 29 à 48 – inserimos um novo conjunto de tags que desenha a tela de alerta modal para que o usuário possa fazer a confirmação da exclusão.
  • Linhas 50 à 59 – inserimos script JQuery que é responsável por recuperar o valor da chave primária ID do paciente passado pelo botão excluir e substituir o valor na propriedade HREF do link/botão de confirmação de exclusão da tela de alerta modal.
<div class="container">
<nav>(menu)</nav><a class="btn btn-primary btn-lg" role="button" href="form.html">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"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td><a class="btn btn-secondary btn-lg" role="button" href="form.html">Alterar</a>
<a class="btn btn-warning btn-lg" role="button" href="#" rel="noopener noreferrer" data-toggle="modal" data-target="#confirmaExclusao">Excluir</a></td>
</tr>
</tbody>
</table>
</div>
<div id="confirmaExclusao" class="modal fade" 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 class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">X</span>
</button>

</div>
<div class="modal-body">

Confirma a exclusão deste registro do Paciente?

</div>
<div class="modal-footer"><a id="btnConf" class="btn btn-warning btn-lg" role="button" href="#">Sim</a>
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancelar</button></div>
</div>
</div>
</div>
</div>
<div id="footer">(footer)</div>
<script th:inline="javascript"><br />
          /*<![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)
                })
           /*]]>*/<br />
        </script>

No lado backend da aplicação, precisamos construir um novo método chamado remover dentro da classe PacienteController. Esse método recebe a anotação @GetMapping(value=”remover/{id}”) indicando que o verbo GET do protocolo HTTP será respondido caso a rota seja http://localhost:8080/remover/. Esse método recebe por parâmetro o valor do ID, e chama o repositório através do método dinâmico pacienteRepository.deleteById(id) para realizar a exclusão do registro no banco de dados. Em seguida o usuário é redirecionado para a tela inicial do CRUD.

package br.com.faltoupontoevirgula.projetospring.controller;

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.Paciente;
import br.com.faltoupontoevirgula.projetospring.repository.PacienteRepository;

@Controller
@RequestMapping("/paciente")
public class PacienteController {

@Autowired
private PacienteRepository pacienteRepository;

@GetMapping("")
public ModelAndView index() {
List listaPaciente = this.pacienteRepository.findAll();

return new ModelAndView("paciente/index","listapac",listaPaciente);
}
@GetMapping("/novo")
public String createForm(@ModelAttribute Paciente paciente) {
return "paciente/form";
}

@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) {
return new ModelAndView("paciente/form","paciente",paciente);
}

@GetMapping(value="remover/{id}")
public ModelAndView remover(@PathVariable ("id") Long id, RedirectAttributes redirect) {
this.pacienteRepository.deleteById(id);
return new ModelAndView("redirect:/paciente");
}
}

Agora acesse o endereço http://localhost:8080/paciente e tente realizar exclusão de um registro.

Com isso concluímos nosso tutorial de construção de um cadastro básico utilizando o Spring Boot.

Teste integrado: interface + controle + modelo

Agora que a aplicação esta basicamente construída, podemos criar testes unitários mais complexos, que validam o processo de funcionamento do crud integrando a interface ao controlador e aos objetos de modelo.
O código inserido nas linhas 51 até 72 cria dois testes integrados:

  • Na linha 54: o teste faz uma requisição do tipo GET para o endereço localhost:8080/paciente,
    então verifica se o status de retorno do servidor foi o código http 200 que é representado pela constante status().isOk(). E ainda no mesmo teste, o código analisa o conteúdo do HTML que foi retornado da seguinte maneira: ele verifica se encontra um tag HTML, dentro deste tag deve haver outro chamado BODY, dentro deste um tag DIV, em seguida dentro deste último tag deve haver outra DIV, e por fim procura a tag TABLE. Se você analisar o código HTML do arquivo index.html é exatamente essa a estrutura desenhada pela view, com esse mecanismo conseguimos validar se os componentes de tela estão sendo criados na forma correta
  • Na linha 60: o teste realiza uma requisição do tipo POST para o endereço localhost:8080/paciente, observe que dois valores são passados para o método:
    post(“/paciente”)
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .param(“form”, “”)
    .content(“id=0&nome=zezinho&sexo=Masculino”)
    O método contentType, define que o tipo da informação enviada são dados de um formulário, o método param cria um parâmetro de URL chamado com com o valor em branco (esse parâmetro é necessário devido a assinatura do método no controller), e por fim o método content passa os dados que viriam pelo formulário.
    E seguida o teste verifica se o status code http é 302 que significa que a requisição foi redirecionada para outra página: .andExpect(status().isMovedTemporarily())
    Por fim ele testa se o endereço de redirecionamento foi para a página principal: .andExpect(view().name(“redirect:/paciente”));
    Com a execução deste teste, deveríamos ter como resultado um novo registro no banco de dados.
  • Na linha 68: realizamos um teste complementar ao anterior, é feita uma requisição GET para o endereço localhost:8080/paciente, onde verificamos se o retorno foi status code 200 andExpect(status().isOk()) e se o conteúdo da página HTML contém uma tabela cuja primeira linha de dados da tabela possui os dados zezinho e masculino respectivamente.
    .andExpect(xpath(“/html/body/div/div/table/tbody/tr/td[1]/text()”).string(“zezinho”))
    .andExpect(xpath(“/html/body/div/div/table/tbody/tr/td[2]/text()”).string(“Masculino”))
package br.com.faltoupontoevirgula.projetospring;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import br.com.faltoupontoevirgula.projetospring.controller.HomeController;
import br.com.faltoupontoevirgula.projetospring.controller.PacienteController;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ProjetoSpringApplicationTests {

@Autowired
private HomeController controller;
@Autowired
private PacienteController pacienteController;
@Autowired
private MockMvc mockMvc;

@Test
public void contextLoads() {
//Verifica a existência da instância do controlador
assertThat(controller).isNotNull();
assertThat(pacienteController).isNotNull();
}

@Test
public void homeControllerTest() throws Exception {
//Teste do método index
this.mockMvc.perform(get("/")).andExpect(status().isOk())
.andExpect(content().string(containsString("eu não acredito")));
}

@Test
public void pacienteControllerTest() throws Exception {
//Teste do método index
this.mockMvc.perform(get("/paciente")).andExpect(status().isOk())
.andExpect(xpath("/html/body/div/div/table").exists());
}

@Test
public void pacienteControllerSaveTest() throws Exception {
this.mockMvc.perform(post("/paciente")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("form", "")
.content("id=0&amp;nome=zezinho&amp;sexo=Masculino"))
.andDo(print())
.andExpect(status().isMovedTemporarily())
.andExpect(view().name("redirect:/paciente"));

this.mockMvc.perform(get("/paciente")).andDo(print()).andExpect(status().isOk())
.andExpect(xpath("/html/body/div/div/table/tbody/tr/td[1]/text()").string("zezinho"))
.andExpect(xpath("/html/body/div/div/table/tbody/tr/td[2]/text()").string("Masculino"));

}

}

Referências:
BOAGLIO, Fernando. Spring Boot Acelere o desenvolvimento de micro serviços. Casa do Código 2017. <https://www.casadocodigo.com.br/products/livro-spring-boot>