Neste codelab você aprenderá a desenvolver um aplicativo Java Web utilizando o framework Spring Boot com Thymeleaf e acesso a dados com JdbcTemplate (SQL DAO - repository), com controle de acesso (autenticação e autorização com Spring Security) que gerencia informações em um banco de dados com operações CRUD padrão: Criar, Recuperar, Atualizar e Excluir. O sistema será hospedado no servidor Heroku.

O que você vai fazer

O que você vai aprender

O que você precisa

Observação: a instalação destas ferramentas está fora do escopo deste documento.

Referências

A aplicação consiste de uma página web para exibir produtos cadastrados previamente. Teremos vários papéis que os usuários poderão desempenhar na aplicação. Vamos ter os seguintes papeis na aplicação:

Papel

Autorização

USUÁRIO

permissão para visualizar todos os produtos

CRIADOR

permissão para criar novos produtos

EDITOR

permissão para editar produtos

ADMIN

todas as permissões

O diagrama de entidades e relacionamentos da aplicação:

A tabela de usuários armazena credenciais e a tabela de papeis armazena autorizações (direitos). O relacionamento de entidade entre usuários e papeis é muitos para muitos porque um usuário pode ter uma ou mais papeis e um papel pode ser atribuído a um ou mais usuários. É por isso que precisamos ter a tabela intermediária usuarios_papeis para representar essa associação muitos para muitos.

Script de criação do banco de dados:

CREATE TABLE IF NOT EXISTS `papeis` (
  `papel_id` INT(11) NOT NULL AUTO_INCREMENT,
  `nome` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`papel_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE IF NOT EXISTS `produto` (
  `produto_id` INT(11) NOT NULL AUTO_INCREMENT,
  `nome` VARCHAR(45) NOT NULL,
  `descricao` VARCHAR(45) NOT NULL,
  `preco` FLOAT NOT NULL,
  PRIMARY KEY (`produto_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE IF NOT EXISTS `usuarios` (
  `usuario_id` INT(11) NOT NULL AUTO_INCREMENT,
  `email` VARCHAR(45) NOT NULL,
  `senha` VARCHAR(64) NOT NULL,
  PRIMARY KEY (`usuario_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE IF NOT EXISTS `usuarios_papeis` (
  `usuario_id` INT(11) NOT NULL,
  `papel_id` INT(11) NOT NULL,
  CONSTRAINT `papel_fk`
    FOREIGN KEY (`papel_id`)
    REFERENCES `papeis` (`papel_id`),
  CONSTRAINT `user_fk`
    FOREIGN KEY (`usuario_id`)
    REFERENCES `autentica-autoriza`.`usuarios` (`usuario_id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


-- Script de inserção de dados

INSERT INTO `papeis` (`nome`) VALUES ('USUARIO');
INSERT INTO `papeis` (`nome`) VALUES ('CRIADOR');
INSERT INTO `papeis` (`nome`) VALUES ('EDITOR');
INSERT INTO `papeis` (`nome`) VALUES ('ADMIN');

INSERT INTO `usuarios` (`email`, `senha`) VALUES ('mercurio@teste.com', '$2a$10$wSa39/yk/UTovsqPt817X.c0I8xlS2s76YQy4ViDxag0mlxUoYUq2');
INSERT INTO `usuarios` (`email`, `senha`) VALUES ('venus@teste.com', '$2a$10$v8Wr0mf6HgmIG0ANimKJOuOIt/09qIkXIF7wCwzq8.U/LTqTs9ovq');
INSERT INTO `usuarios` (`email`, `senha`) VALUES ('terra@teste.com', '$2a$10$SXuWtufjZVvmMFpc56HMEObBhX/vov8UJxUBeZX3RoEnOIR0yqdH6');
INSERT INTO `usuarios` (`email`, `senha`) VALUES ('marte@teste.com', '$2a$10$Wl1gojjJgFhXztvHIULT3e0hiEMrDbCWCys0p6LnfrqxcxYkgh9OW');
INSERT INTO `usuarios` (`email`, `senha`) VALUES ('jupiter@teste.com', '$2a$10$5sci59bfdcED4XxxuN9gx.SJBPsdNknirJSkLbTCouf2mFzLmX/Gi');

INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (1, 1); -- usuário mercurio tem papel USUARIO
INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (2, 2); -- usuário venus tem papel CRIADOR
INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (3, 3); -- usuário terra tem papel EDITOR
INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (4, 2); -- usuário marte tem papel CRIADOR
INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (4, 3); -- usuário marte tem papel EDITOR
INSERT INTO `usuarios_papeis` (`usuario_id`, `papel_id`) VALUES (5, 4); -- usuário jupiter tem papel ADMIN

Vamos usar o Maven para gerenciar as dependências do projeto e facilitar o processo de construção da aplicação. Maven usa um arquivo XML chamado pom.xml. Um modelo de objeto de projeto ou POM é a unidade fundamental de trabalho no Maven. É um arquivo XML que contém informações sobre o projeto e detalhes de configuração usados ​​pelo Maven para construir o projeto. O uso do framework Spring Boot permite o desenvolvimento rápido de aplicações.

Neste projeto vamos usar as dependências

O arquivo pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.6.1</version>
                <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>br.edu.iftm</groupId>
        <artifactId>autentica-autoriza</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>autentica-autoriza</name>
        <description>Demo project for Spring Boot</description>
        <properties>
                <java.version>8</java.version>
        </properties>
        <dependencies>

                <!-- dependência para usar JdbcTemplate -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-jdbc</artifactId>
                </dependency>

                <!-- dependência para usar engine template Thymeleaf -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-thymeleaf</artifactId>
                </dependency>

                <!-- dependencia app web mvc -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
        
                <!-- dependências de segurança-->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.thymeleaf.extras</groupId>
                        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
                </dependency>

                <!-- dependência conector banco de dados mysql -->
                <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <scope>runtime</scope>
                </dependency>

                <!-- dependências de testes -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-test</artifactId>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <groupId>org.springframework.security</groupId>
                        <artifactId>spring-security-test</artifactId>
                        <scope>test</scope>
                </dependency>

                <!-- dependências ferramentas desenvolvedor -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-devtools</artifactId>
                        <scope>runtime</scope>
                        <optional>true</optional>
                </dependency>

                <!-- Validação -->
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-validation</artifactId>
                </dependency>

        </dependencies>

        <build>
                <plugins>
                        <plugin>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-maven-plugin</artifactId>
                                <!-- configuração para upload automatico de resources. Executar o app com maven:spring-boot:run -->
                                <configuration>
                                        <addResources>true</addResources>
                                </configuration>
                        </plugin>
                </plugins>
        </build>
</project>

O arquivo src\main\resources\application.properties contém informações para o framework Spring.

A configuração #1 estabelece as informações para conexão ao banco de dados. A configuração #2 permite a visualização do log do servidor no console. A configuração #3 permite a criação automática das tabelas do banco de dados e a configuração #4 habilita a página de erro que deve ser exibida quando alguma exceção acontecer.

#1 configuração da fonte de dados 
spring.datasource.url=jdbc:mysql://localhost:3306/autentica-autoriza
spring.datasource.username=root
spring.datasource.password=

#2 configuração de log da aplicação. 
logging.level.root=INFO
logging.level.org.springframework.jdbc.core.JdbcTemplate=TRACE
logging.level.web=TRACE

#3 O banco de dados deve ser criado antes de executar o app. 
# O nome do banco de dados deverá ser autentica-autoriza. O arquivo com o script de criação das tabelas deve
# ter o seguinte nome e localização src\main\resources\schema.sql
spring.sql.init.mode=always

#4 página de erro src\main\resources\templates\error.html
server.error.whitelabel.enabled=false
server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always
server.error.include-binding-errors=always

Notas da aula

O site tem a seguinte estrutura

A tela foi dividida em três partes

No projeto teremos um layout padrão que será usado por todas as páginas da aplicação. Assim na estrutura de pastas vamos criar dois arquivos de templates: um para conter os fragmentos e outro para conter o layout.

O arquivo fragmentos.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

<div th:fragment="navbar" class="navbar navbar-dark navbar-expand-md bg-dark mb-1">
  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
    aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon "></span>
  </button>
  <div class="container d-flex">
    <div class="collapse navbar-collapse col-6" id="navbarSupportedContent">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link text-white m-1 material-icons" href="/" title="Início">home</a>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
            data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            Usuários
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdown">
            <a class="dropdown-item" href="/novo-usuario">Novo</a>
            <a class="dropdown-item" href="/usuarios">Todos</a>
          </div>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"
            data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            Produtos
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdown">
            <a class="dropdown-item" href="/novo-produto">Novo</a>
            <a class="dropdown-item" href="/produtos">Todos</a>
          </div>
        </li>
      </ul>
    </div>

    <h5 class="text-white col-4 mb-0">Projeto Sistema Web MVC SQL</h5>

    <div class="d-flex col-2" sec:authorize="isAuthenticated()">
      <span class="text-white small" sec:authentication="name"></span>
    </div>
    <div>
    <form th:action="@{/logout}" method="post" class="ml-4" sec:authorize="isAuthenticated()">
      <input class="btn btn-secondary btn-sm p-1 small" type="submit" value="Sair" />
    </form>
    </div>
  </div>
</div>

<div th:fragment="rodape">
  <button type="button" class="btn btn-dark" data-bs-toggle="modal" data-bs-target="#sobre">
    Quem somos
  </button>

  <!-- Modal -->
  <div class="modal fade" id="sobre" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLongTitle">Projeto Sistema Web MVC e SQL</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <p>No segundo período o estudante desenvolverá um sistema web monolítico utilizando template engine MVC e
            acesso a dados
            baseado em comandos SQL. A concepção deste projeto foi para que o estudante tenha a oportunidade tanto de
            utilizar uma
            tecnologia de template engine, bem como aplicar na prática o acesso a dados de uma forma mais manual, via
            comandos SQL, sem
            ainda recorrer a ferramentas modernas de mapeamento objeto-relacional.</p>
          <p>Tecnologias utilizadas neste sistema:</p>
          <ul>
            <li>Spring boot</li>
            <li>Maven</li>
            <li>MySQL</li>
            <li>GitHub</li>
            <li>Javascript</li>
            <li>JQuery</li>
            <li>MVC</li>
            <li>Bootstrap</li>
            <li>Spring Security</li>
            <li>Spring Data Access</li>
            <li>Heroku Server</li>
          </ul>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fechar</button>
        </div>
      </div>
    </div>
  </div>
</div>

</html>

O arquivo layout.html

<html xmlns:th="http://www.thymeleaf.org" th:fragment="layout">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

<title>Escola 5.3</title>
</head>
<body class="d-flex flex-column min-vh-100">

        <!-- Início do conteúdo da página -->
        <nav th:insert="fragmentos/fragmentos :: navbar"></nav>

        <main th:insert="this :: conteudo" class="ml-3"></main>

        <footer th:insert="fragmentos/fragmentos :: rodape" class="mt-auto mx-auto my-3"></footer>
        <!-- Fim do conteúdo da página -->
        
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>

</html>

Toda página nova deverá ter o seguinte código inicial

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="fragmentos/layout">
<div th:fragment="conteudo">
    <!-- insira seu código aqui -->
</div>

</html>

O seu código deverá ser inserido depois do comentário

Referências:

Criação do modelo

O modelo é uma classe Java que representa uma entidade do banco de dados. Chaves estrangeiras no banco de dados são representadas no modelo por um atributo da classe do tipo da tabela referenciada.

Criação do repositório

O repositório é uma classe Java que realiza as transações com o banco de dados fazendo a "ponte" entre os mundos dos objetos e o mundo das tabelas. Aqui são implementados métodos para efetivar as operações CRUD. Estas classes devem ser anotadas com @Repository.

Criação do controlador

Nestas classes encontramos os métodos que implementam a lógica MVC para atender às solicitações do usuário do sistema. Estas classes são anotadas com @Controller e seus métodos podem ser anotados com @GetMapping ou @PostMapping, dependendo do tipo de requisição HTTP vinda do front-end. A interação do usuário com o sistema se dá através de links ou botões. Cada link ou botão de formulário define uma URL com uma parte (chamada path, veja figura abaixo) que é utilizada para mapear qual método atenderá a solicitação do usuário.

fonte: https://pzwiki.wdka.nl/mediadesign/Universal_Resource_Locator

Criação dos templates

Referências

Os templates são arquivos HTML que utilização atributos Thymeleaf e representam a letra V do MVC.

Referências:

Vamos usar a autenticação de nome de usuário / senha, que utiliza um formulário HTML para obter do usuário um nome e uma senha para autenticação.

Quando a página de login é especificada na configuração do Spring Security, você é responsável por renderizar a página.

Existem alguns pontos-chave sobre o formulário HTML padrão:

Primeiro vamos adicionar uma nova pasta à nossa estrutura do projeto

Configurando a segurança com WebSecurityConfigurerAdapter

package br.edu.iftm.autenticaautoriza.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebConfigSecurity extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailsService();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                 .antMatchers("/usuarios").authenticated()
                 .anyRequest().permitAll()
                .and()
            .formLogin()
                 .loginPage("/login")
                 .defaultSuccessUrl("/")
                 .permitAll()
                 .and()
            .logout()
                 .logoutSuccessUrl("/")
                 .permitAll();
    }
}

Implementar a interface UserDetails

package br.edu.iftm.autenticaautoriza.security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import br.edu.iftm.autenticaautoriza.model.Usuario;

public class MyUserDetails implements UserDetails {

    private Usuario usuario;

    public MyUserDetails(Usuario usuario) {
        this.usuario = usuario;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return usuario.getSenha();
    }

    @Override
    public String getUsername() {
        return usuario.getEmail();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

Implementar a interface UserDetailsService

package br.edu.iftm.autenticaautoriza.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import br.edu.iftm.autenticaautoriza.model.Usuario;
import br.edu.iftm.autenticaautoriza.repository.UsuarioRepository;

public class MyUserDetailsService implements UserDetailsService {
 
    @Autowired
    private UsuarioRepository usuarioRepository;
     
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Usuario usuario = usuarioRepository.buscaPorEmail(username);
        if (usuario == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new MyUserDetails(usuario);
    }
}

Adicionar um controle para /login

@GetMapping("/login")
    public String login() {
        return "login";
}

Adicionar um template com formulário para o login

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="fragmentos/layout">
<div th:fragment="conteudo">
    <!-- insira seu código aqui -->
    <div class="container col-6">
        <h1>Autenticação (login)</h1>
        <div th:if="${param.error}">
            Usuario/senha inválidos</div>
        <div th:if="${param.logout}">
            Você foi logged out</div>
        <form th:action="@{/login}" method="post">
            <div class="form-group">
                <input class="form-control" type="text" name="username" placeholder="E-mail" />
            </div>
            <div class="form-group">
                <input class="form-control" type="password" name="password" placeholder="Senha" />
            </div>
            <input class="btn btn-primary mt-3" type="submit" value="Log in" />
        </form>
    </div>
</div>

</html>

Alteração do fragmento da barra de navegação para adicionar botão de logout e saudação do usuário logado

<div class="d-flex col-2" sec:authorize="isAuthenticated()">
    <span class="text-white small" sec:authentication="name"></span>
</div>
<div>
    <form th:action="@{/logout}" method="post" class="ml-4" sec:authorize="isAuthenticated()">
        <input class="btn btn-secondary btn-sm p-1 small" type="submit" value="Sair" />
    </form>
</div>

Recuperar os detalhes do usuário no Spring Security

https://www.baeldung.com/get-user-in-spring-security

Introdução

Uma característica comum em todos programas de computador é a necessidade de tratar exceções. Em Java as exceções são tratadas por um mecanismo de lançamento de exceções quando o erro é identificado. Spring Boot oferece diversas maneiras de tratar exceções. Uma forma simples e poderosa que usaremos aqui neste tutorial é a anotação @ControllerAdvice.

Esta anotação facilita nossa vida para lidar com todos os tipos de exceções em um local central em nossa aplicação. A facilidade está em não precisar capturar exceções em cada método ou classe separadamente, bastando apenas lançar a exceção no método e, em seguida, ela será capturada na classe central de tratamento de exceções anotada por @ControllerAdvice.

Lançando exceções

Um exemplo de erro (exceção)

Divisão por zero - java.lang.ArithmeticException: / by zero

Depois de executar o programa acima teremos o seguinte:

Como tratar este erro? Vamos modificar o código anterior para exemplificar

Bloco try/catch não deixa o programa parar, tratando o erro no local onde aconteceu. Se as exceções capturadas forem de mesma hierarquia, as classes mais especializadas devem aparecer primeiro. Para capturar qualquer exceção use a classe Exception (não indicado):

catch (Exception e) { ... }

Podemos também delegar o tratamento do erro para outro método na pilha de execução, para isso podemos lançar uma exceção .

A central de tratamento de exceções

A classe que será criada para fazer o papel de central de tratamento de exceções deverá ser anotada com a anotação @ControllerAdvice. Nesta classe vamos usa a seguinte anotação em seus métodos:

Esta anotação vai funcionar como um filtro para capturar uma exceção específica, possibilitando o tratamento do erro.

Classe @ControllerAdvice

package br.edu.iftm.autenticaautoriza.controller;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@ControllerAdvice
public class MyControllerAdvice {

    @ExceptionHandler(java.sql.SQLIntegrityConstraintViolationException.class)
    public String loginNameDuplicado(java.sql.SQLIntegrityConstraintViolationException ex,
            final RedirectAttributes redirectAttributes) {
        String errorMessage = ex.getMessage();
        if (errorMessage.contains("Duplicate entry")) {
            redirectAttributes.addFlashAttribute("mensagem", "E-mail já cadastrado. Tente cadastrar outro e-mail");
            return "redirect:/novo-usuario";
        }
        return "error";
    }
}

Esta classe deve estar no mesmo pacote das classes controladoras (pacote controller).

Referência:

A validação de dados obtidos através de formulários HTML deve ser realizada para garantir a integridade dos dados em aplicações web.

É necessário garantir que intervalos de valores são respeitados ou que formatos são seguidos. Por exemplo, queremos ter certeza de que o usuário não tem -345 anos ou que seu endereço de e-mail é válido.

Existem muitas maneiras de validar os dados do formulário - e o método que você usa depende do seu aplicativo. Em geral, você desejará realizar validação do lado do cliente, bem como validação do lado do servidor. A validação do lado do cliente garante que os dados não filtrados nem cheguem ao back-end, enquanto a validação do lado do servidor garante que os dados errados não sejam processados posteriormente.

Começamos com os modelos de domínio, com a aplicação de anotações nos campos das classes que queremos validar.

1. Criar uma conta no Heroku

2. Criar um app no Heroku

No canto superior direito da tela principal, clique em New e escolha Create new app.

Depois de preencher os campos App name e Choose a region, clique no botão Create App

3. Método de implantação - Deploy

Escolha o deployment method GitHub, que permitirá a automação dos próximos deployments quando o repositório GitHub for atualizado.

4. Conexão com o GitHub

Você deve indicar o endereço do repositório GitHub do projeto, veja figura abaixo. Informe o nome do repositório e clique em Search

Após informar o repositório GitHub, Heroku informará o estabelecimento da conexão conforme figura abaixo:

Na mesma tela clique no botão Enable Automatic Deploys (veja figura abaixo)

Você pode verificar a situação de sua app na aba Overview:

Você já pode acessar a app usando o botão Open app no canto superior direito da página:

Você verá a sua app sendo executada na Web no endereço: :

5. Configurando o banco de dados

No seu DashBoard , vá em Resources e no campo Add-ons digite mysql e adicione o recurso ClearDB Mysql:

6. Gerando a app em arquivo jar

Com o seu projeto aberto no vscode, vá em MAVEN/nome do projeto/Lifecycle e escolha package

Quando terminar de empacotar o projeto em um arquivo JAR, você verá no terminal:

Na pasta \target você verá o arquivo JAR gerado

Copie o nome do arquivo gerado (neste exemplo foi demo-0.0.1-SNAPSHOT.jar).

7. O arquivo Procfile

Após a criação do pacote JAR você deve criar um arquivo chamado Procfile na raiz do seu projeto, veja figura abaixo:

Neste arquivo você vai inserir a linha abaixo

web: java -Dserver.port=$PORT -Dspring.profiles.active=prod -jar target/[nome do arquivo .jar gerado no passo anterior]

8. Obtendo a URL de conexão com o banco de dados

Vá ao dashboard do Heroku e selecione a aba Settings:

Em Settings role a tela para baixo até exibir o botão Reveal Config Vars:

Clique neste botão e veremos a url de conexão do BD:

Agora vamos copiar o arquivo de configuração da app src\main\resources\application.properties e renomear a cópia para application-prod.properties

Você vai copiar o texto da caixa mostrada em vermelho na figura acima e colar no arquivo src\main\resources\application-prod.properties, conforme mostrado abaixo (a url de conexão é o valor da propriedade spring.datasource.url :

#MySQL
spring.datasource.url = mysql://a129f37d8b1a49:a99f4aa6@us-cdbr-east-05.cleardb.net/heroku_a5fef719c15dc8d?reconnect=true
spring.jpa.show-sql=false 
spring.jpa.hibernate.ddl-auto=update

O seu arquivo application-prod.properties ficará assim:

spring.datasource.url = mysql://a129f37d8b1a49:a99f4aa6@us-cdbr-east-05.cleardb.net/heroku_a5fef719c15dc8d?reconnect=true
spring.jpa.show-sql=false 
spring.jpa.hibernate.ddl-auto=update

# logs
logging.level.root=info
logging.level.org.springframework.jdbc.core.JdbcTemplate=debug
logging.level.web=DEBUG

# retire o comentário da linha abaixo para criar as tabelas do banco de 
# dados. O banco de dados deve ser criado antes de executar o servidor. 
# O nome do banco de dados deverá ser autentica-autoriza
spring.sql.init.mode=always

# página de erro
server.error.whitelabel.enabled=false
server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always
server.error.include-binding-errors=always

Com os dois novos arquivos criados, o seu projeto ficará assim:

Você pode atualizar o GitHub com as novas alterações no projeto e após um breve espaço de tempo você verá que o Heroku realizou o deploy da app automaticamente.

Referência:

Em desenvolvimento