Spring Boot

Estrutura e prática Spring Boot framework

Resumo em construção
Voltar

Material complementar:


Spring Boot:

Camada que atua sob framework Spring, simplificando-o e modernizando-o, seguindo padrão MVC. Spring implementa padrão IoC (Inversion of Control), onde os objetos (Spring Beans / Beans) são instanciados automaticamente pelo container do Spring, dispensando criação manual.


Pré-requisitos (Windows):

JDK (Java Development Kit) é kit de ferramentas e bibliotecas para desenvolver aplicações Java. Gradle é automatizador de build moderno que usa linguagem baseada em Groovy ou Kotlin para configurar projetos. Maven é mais antigo que Gradle, gerenciando builds e dependências para projetos Java baseada em estrutura padronizada e arquivos XML de configuração.

  1. JDK: Acesse
    1. Baixar e instalar JDK Oracle
    2. Win + R, digite "SystemPropertiesAdvanced", acessar Variáveis de Ambiente
    3. Aba "Variáveis do sistema", em Novo, criar/editar variáveis:
        Criar:
      • Nome: JAVA_HOME
      • Valor: Caminho da pasta com JDK instalado (Ex: "C:\Program Files\Java\jdk-24")
      • Confirmar criação OK

      • Editar:
      • Nome: Path
      • Clicar em Novo
      • Valor: %JAVA_HOME%\bin
      • Confirmar criação OK
    4. Verificar instalação no CMD, com comando "java -version"
  2. Gradle: Acesse
    1. Baixar e instalar Gradle: site oficial Gradle, Install, Install manually, Step 1, baixar Binary-only
    2. Diretório "C:/", crie pasta "Gradle", e descompacte o Gradle dentro dela
    3. Win + R, digite "SystemPropertiesAdvanced", acessar Variáveis de Ambiente
    4. Aba "Variáveis do sistema", editar variável Path: Editar:
      • Nome: Path
      • Clicar em Novo
      • Valor: C:\Gradle\gradle-VERSAO\bin
      • Confirmar criação OK
    5. Verificar instalação: Win + R, digitar CMD, e informar comando "gradle -v"
  3. Maven: Acesse
    1. Baixar e instalar Maven: site oficial Maven, página de Download, em Files, baixar de "Binary zip archive"
    2. Diretório "C:/", crie pasta "Maven", e descompacte o Gradle dentro dela
    3. Win + R, digite "SystemPropertiesAdvanced", acessar Variáveis de Ambiente
    4. Aba "Variáveis do sistema", editar variável Path: Editar:
      • Nome: Path
      • Clicar em Novo
      • Valor: C:\Maven\apache-maven-VERSAO\bin
      • Confirmar criação OK
    5. Verificar instalação: Win + R, digitar CMD, e informar comando "mvn -v"
  4. Recomenda-se utilizar IDE Jetbrains IntelliJ: Acesse

Padrão MVC:

  • Model: camada de modelo/negócios responsável por implementar regras de negócio da aplicação, definição das entidades e relacionamentos, implementando comportamento e camada de dados;
  • View: camada de visão, responsável pela implementação e exibição de interface com usuário;
  • Controller: camada de controle, classe OOP responsável pelo intermédio entre Model e View, pois elas nunca comunicam-se diretamente.
Fluxo:
  1. Controller recebe requisição HTTP;
  2. Controller trata dados da requisição, interagindo com Model (se necessário);
  3. Controller prepara/envia resposta HTTP ao cliente, geralmente invocando View para tal.
Spring Boot:

Recomenda-se separar camadas criando-as em diferentes pacotes, dentro do pacote principal.


Pacote Controller ficará em "projeto/src/main/java/pacotePrincipal/pacoteController"
Nome do pacotePrincipal: grupoExemplo.com.nomeProjeto
Nome do pacoteController: grupoExemplo.com.nomeProjeto.pacoteController

Estrutura:

  • raizProjeto/
    • src/: códigos do projeto
      • main/: arquivos para programar
        • java/: armazena pacote principal do projeto
          • pacotePrincipal.com/: pacote principal do projeto, aqui dentro ficam outros pacotes de controllers, com seus respectivos controllers (Arquivos.java)
            • NomeAplicacao.java: classe principal do pacote principal
        • resources/:
          • application.properties: variáveis de configuração do projeto
      • tests/: arquivos de testes unitários
    • build.gradle: dependências do projeto e testes
    • gradlew: executar servidor Gradle

Criar projeto Spring Boot:

Acesse o site Spring Initializr, e preencha os campos:

  1. Project: Gradle - Groovy
  2. Project Metadata
    • Group: grupoExemplo.com
    • Artifact: proj1
    • Name: proj1 (igual no Artifact)
    • Package name: grupoExemplo.com.proj1
  3. Java: versão do JDK instalado na máquina
  4. Dependencies:
    • DevTools
    • Spring Web
  5. Generate (o projeto será gerado)
  6. Extrair projeto localmente, abrir com editor, e executar terminal na pasta raíz do projeto (mesmo local do 'gradlew'): ".\gradlew bootRun"
    • Quando a execução ficar em "80% EXECUTING", acesse em "http://localhost:8080"

ORM:

Object-Relational Mapping é padrão para integrar SQL ao código Java na aplicação, sem necessidade de códigos SQL (opcional). ORM visa abstrair camda de dados da aplicação. Tables geradas via definição de classes. Diferença entre OOP e SQL, respectivamente:

    Transformações:
  • Classe/Entidade - tabela;
  • Atributo - coluna;
  • Objeto - registro;
  • Cada tabela possui chave primária.
    Relacionamentos:
  • (1:n): table com 'n' recebe chave estrangeira referenciando chave primária da table '1'. Ex: Item(n) e Pedido(1), cria-se fk 'idPedido' em Item;
  • (1:1): escolhe-se 1 table para receber chave estrangeira referenciando chave primária da outra table. Ex: Servidor(1) e Campus(1), escolheu-se criar fk 'idCampus' em Servidor;
  • (n:n): cria-se nova table, referenciando relacionamento ocorrido, contendo 2 chaves estrangeiras, cada uma referenciando chave primária das tables relacionadas.

Hibernate:

Framework Java que implementa padrão ORM, via especificações e pacotes JPA (Java Persistence API). Incorporar Hibernate JPA e MySQL em projeto Spring Boot:

  1. 'build.gradle', em 'dependencies', incluir linhas:
    
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
    
  2. Criar BD via MySQL diretamente (CREATE DATABASE 'nomeBD');
  3. Em 'src/main/resources/application.properties', informar:
    1. Modo de criação das tables, escolhendo:
      • create: quando aplicação iniciada, criará tables (se inexistentes);
      • create-drop: quando aplicação iniciada, criará tables (se inexistentes), e quando aplicação finalizada, excluirá tables;
      • update: semelhante ao create, verificando também necessidade de novas atualizações nas tables criadas;
      • none: nada é feito em BD.
      
      spring.jpa.hibernate.ddl-auto=update
      
    2. URL conexão BD:
      
      spring.datasource.url=jdbc:mysql://localhost:3306/nomeBD
      
    3. Login do BD:
      
      spring.datasource.username=nomeUsuarioBD
      spring.datasource.password=senhaUsuarioBD
      
    4. Driver JDBC:
      
      spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
      
  4. Mapear classes entidades (somente entidades usarão Hibernate)
    1. Informar '@Entity' acima da delcaração da classe entidade e importar Jakarta:
      
      import jakarta.persistence.Entity;
      
      @Entity
      public class NomeEntidade {
      }
      
    2. Indicar atributo da entidade que criará chave primária. Geralmente, cria-se atributo 'id' e referencia-o com @Id (herdará nas subclasses, importá-lo somente na superclasse), com notação de gerá-lo automaticamente e importar pacotes necessários:
      
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      
      @Entity
      public class Plano {
          @Id
          @GeneratedValue(strategy=GenerationType.AUTO)
          private long id;
      }
      
    3. Atributos Date e Calendar, informar anotação '@Temporal' acima da respectiva declaração:
      • TemporalType.DATE: somente data;
      • TemporalType.TIME: somente hora;
      • TemporalType.TIMESTAMP: data e hora.
      
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      
      @Entity
      public class Plano {
          @Id
          @GeneratedValue(strategy=GenerationType.AUTO)
          private long id;
      
          @Temporal(TemporalType.TIMESTAMP)
          private Date dataHora;
      
          @Column(name="NOME_PLANO", length=50, nullable=false, unique=false)
          private String nome;
      
          @Enumerated(EnumType.STRING)
          private Gender genero;
      
          @Transient
          private Integer idade; // Não armazenado em BD
      }
      
  5. Mapear relacionamentos entre entidades:
    • (1:n) unidirecional: entidade todo (da relação 1), informar '@OneToMany' no atributo que indica parte do relacionamento, seguido da identificação da chave estrangeira presente na tabela parte (da relação n);
      
      @Entity
      public class Operadora {
          @OneToMany
          @JoinColumn(name="operadora_id")
          Set<Plano> planos;
      }
      
    • (1:n) bidirecional: entidade parte, informar '@ManyToOne', criando chave estrangeira na tabela parte. Entidade todo, informar '@OneToMany' e mapeamento através do atributo chave estrangeira. Considere Endereco (parte) e Cliente (todo);
      
      @Entity
      public class Endereco {
          @ManyToOne
          Cliente cliente;
      }
      
      @Entity
      public class Cliente {
          @OneToMany(mappedBy="cliente")
          Set<Endereco> enderecos;
      }
      
    • (n:1) unidirecional: entidade todo, informar '@ManyToOne' no atributo da parte. Considere Endereco a 1 Cliente, Cliente a n Endereços, porém desconheça seus Endereços, então Endereco é todo;
      
      @Entity
      public class Endereco {
          @ManyToOne
          Cliente cliente;
      }
      
    • (n:1) bidirecional: semelhante ao 1:n bidirecional;
    • (n:n) unidirecional: entidade todo, informar '@ManyToMany' no atributo da parte. Considere Conta n (todo) e Cliente n (parte), e Cliente desconheça suas Contas;
      
      @Entity
      public class Conta {
          @ManyToMany
          Set<Cliente> clientes;
      }
      
    • (n:n) bidirecional: informar '@ManyToMany' em ambas entidades nos atributos correspondentes, e informar mapeamento na entidade parte. Conta n (todo) e Cliente n (parte);
      
      @Entity
      public class Conta {
          @ManyToMany
          Set<Cliente> clientes;
      }
      
      @Entity
      public class Cliente {
          @ManyToMany(mappedBy = "clientes")
          Set<Conta> contas;
      }
      
    • (1:1) unidirecional: entidade todo, informar '@OneToOne' no atributo da parte. Considere Campus (todo) e Servidor (parte), e Servidor desconheça Campus;
      
      @Entity
      public class Campus {
          @OneToOne
          Servidor diretor;
      }
      
    • (1:1) bidirecional: informar '@OneToOne' em ambas entidades nos atributos correspondentes, e informar mapeamento na entidade parte. Campus n (todo) e Servidor n (parte);
      
      @Entity
      public class Campus {
          @OneToOne
          Servidor diretor;
      }
      
      @Entity
      public class Servidor {
          @OneToOne(mappedBy = "diretor")
          Campus campusDirigido;
      }
      
    • Herança: single table, onde cria-se tabela única para todas subclasses com superclasse em comum, contendo colunas para cada atributo da superclasse, colunas para cada atributo das subclasses, e coluna "descriminatória" determinando a qual entidade "pertence" cada registro. Superclasse, informar anotação de herança single table e de coluna descriminatória. Subclasses, informar anotação de valor descriminador, apontando valor para coluna descriminatória da superclasse. Conforme abaixo, gerará tabela única para as 3 entidades, contendo colunas para atributos de Cliente, colunas para atributos de ClientePessoaFisica, colunas para atributos de ClientePessoaJuridica, e coluna descriminatória com nome class. Registros de ClientePessoaFisica serão armazenados em class com valor ClientePessoaFisica, e o mesmo para ClientePessoaJuridica, possibilitando queries SELECT com WHERE="ClientePessoaFisica" e "ClientePessoaJuridica".
      
      @Entity
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name = "class")
      public class Cliente {
      }
      
      @Entity
      @DiscriminatorValue(value="ClientePessoaFisica")
      public class ClientePessoaFisica {
      }
      
      @Entity
      @DiscriminatorValue(value="ClientePessoaJuridica")
      public class ClientePessoaJuridica {
      }
      


Projeto 1: Controllers

Projeto para conhecimento de estrutura Spring Boot, e geração de controllers. Gerar projeto Spring Boot Gradle-Groovy, com dados:

  • Group: ubsocial.com
  • Artifact: proj1
  • Name: proj1
  • Package: ubsocial.com.proj1
  • Dependencies:
    • DevTools
    • Spring Web
  • Generate (projeto será gerado), extrair, e abrir com IntelliJ
  • No IntelliJ, dentro da pasta do pacote principal (ubsocial.com.proj1), criar pacote "controller": ubsocial.com.proj1.controller
  • Dentro do pacote "controller", criar controller (classe) "HelloController.java", com o conteúdo:
    
    package ubsocial.com.proj1.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @GetMapping("/hello")
        public String hello() {
            return "Hello World!";
        }
    
    }
    
  • Na mesma pasta de "gradlew", executar projeto: ".\gradlew bootRun"
  • Dentro do pacote controller, criar pacote CalendarController (será classe CalendarController.java), com o conteúdo:
    
    package ubsocial.com.proj1.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class CalendarController {
    
        @GetMapping("/today")
        public String today() {
            return "Hoje é " + java.time.LocalDate.now();
        }
    
        @GetMapping("/tomorrow")
        public String tomorrow() {
            return "Amanhã é " + java.time.LocalDate.now().plusDays(1);
        }
    
    }
    
  • Na mesma pasta de "gradlew", executar projeto: ".\gradlew bootRun"


Projeto 2: Models

Projeto de operadora telefônica, com objetivo de criar aplicação com Models e incorporar, em código, relacionamentos entre classes Java, conforme acima. Relacionamentos unidirecionais (agregação, composição), somente o todo (losango) conhece sua parte (linha com seta), então será criado dado na classe do todo, cujo nome é o nome do relacionamento apontando para parte. Gerar projeto Spring Boot Gradle-Groovy, com dados:

  • Group: ubsocial.com
  • Artifact: telefonia
  • Name: telefonia
  • Package: ubsocial.com.telefonia
  • Dependencies:
    • DevTools
    • Spring Web
  • Generate (projeto será gerado), extrair, e abrir com IntelliJ
  • No IntelliJ, dentro da pasta do pacote principal (ubsocial.com.telefonia), criar pacote "model": ubsocial.com.telefonia.model
  • Dentro do pacote "model", criar pacote de entidades "entity": ficará model.entity
  • Dentro do pacote "model.entity", criar Model (entidade) Operadora:
    
    package ubsocial.com.telefonia.model.entity;
    
    public class Operadora {
        private String nome;
    }
    
  • Dentro do pacote "model.entity", criar pacote "pessoal": ficará model.entity.pessoal
  • Dentro do pacote "pessoal", criar Model Cliente:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    
    public class Cliente {
        private String nome;
        private String endereco;
    }
    
  • Dentro do pacote "pessoal", criar Model ClientePessoaFisica:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    
    public class ClientePessoaFisica extends Cliente {
        private String cpf;
    }
    
  • Dentro do pacote "pessoal", criar Model ClientePessoaJuridica:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    
    public class ClientePessoaJuridica extends Cliente {
        private String cnpj;
    }
    
  • Dentro do pacote "model.entity", criar pacote "comercial": ficará model.entity.comercial
  • Dentro do pacote "comercial", criar Model Celular:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import ubsocial.com.telefonia.model.entity.pessoal.Cliente;
    import java.util.Set;
    
    public class Celular {
        private long numero;
        private Cliente cliente;
        private Set ligacoes;
        private Plano plano;
    }
    
  • Dentro do pacote "comercial", criar Model CelularPosPago:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    
    public class CelularPosPago extends Celular {
        private int diaVencimento;
    }
    
  • Dentro do pacote "comercial", criar Model CelularPrePago:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import java.util.Date;
    
    public class CelularPrePago extends Celular {
        private double saldo;
        private Date dataDeValidade;
    }
    
  • Dentro do pacote "comercial", criar Model Ligacao:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import java.util.Date;
    
    public class Ligacao {
        private Date data;
        private int duracao;
    }
    
  • Dentro do pacote "comercial", criar Model Plano:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    
    public class Plano {
        private String nome;
        private double valorPorMinuto;
    }
    


Projeto 3: ORM

Inclusão de padrões Hibernate JPA ORM no projeto 2.

  1. Criar banco de dados MySQL: CREATE DATABASE telefonia;
    • Nesse exemplo, usou-se MySQL do XAMPP, cujo padrão user=root e não possui password.
  2. 'build.gradle', adicionar novas dependencies:
    
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
    
  3. 'src/main/resources/application.properties':
    
    spring.application.name=telefonia
    spring.jpa.hibernate.ddl-auto=update
    spring.datasource.url=jdbc:mysql://localhost:3306/telefonia
    spring.datasource.username=root
    spring.datasource.password=
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
  4. Na mesma pasta de "gradlew", executar projeto: ".\gradlew bootRun";
  5. Mapear classes (conforme códigos no final):
    1. Mapear classes como Entity (com exceção de 'TelefoniaApplication');
    2. Informar atributo 'id' e gerador (com exceção das subclasses);
    3. Formatar valores com data;
    4. Mapear relacionamentos entre entidades;
  6. Na mesma pasta de "gradlew", executar projeto: ".\gradlew bootRun";
    • Verificar tables criadas no banco de dados MySQL
  • Operadora:
    
    package ubsocial.com.telefonia.model.entity;
    import ubsocial.com.telefonia.model.entity.comercial.Celular;
    import ubsocial.com.telefonia.model.entity.comercial.Plano;
    import ubsocial.com.telefonia.model.entity.pessoal.Cliente;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import jakarta.persistence.JoinColumn;
    import jakarta.persistence.OneToMany;
    import java.util.Set;
    
    @Entity
    public class Operadora {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
        private String nome;
        @OneToMany
        @JoinColumn(name = "operadora_id")
        private Set<Cliente> clientes;
        @OneToMany
        @JoinColumn(name = "operadora_id")
        private Set<Celular> celulares;
        @OneToMany
        @JoinColumn(name = "operadora_id")
        private Set<Plano> planos;
    }
    
  • comercial/Celular:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import ubsocial.com.telefonia.model.entity.pessoal.Cliente;
    import jakarta.persistence.DiscriminatorColumn;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import jakarta.persistence.Inheritance;
    import jakarta.persistence.InheritanceType;
    import jakarta.persistence.JoinColumn;
    import jakarta.persistence.ManyToOne;
    import jakarta.persistence.OneToMany;
    import java.util.Set;
    
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "class")
    @Entity
    public class Celular {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
        private long numero;
        @ManyToOne
        private Cliente cliente;
        @OneToMany
        @JoinColumn(name = "celular_id")
        private Set<Ligacao> ligacoes;
        @ManyToOne
        private Plano plano;
    }
    
  • comercial/CelularPosPago:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import jakarta.persistence.DiscriminatorValue;
    import jakarta.persistence.Entity;
    
    @Entity
    @DiscriminatorValue(value = "CelularPosPago")
    public class CelularPosPago extends Celular {
        private int diaVencimento;
    }
    
  • comercial/CelularPrePago:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import java.util.Date;
    import jakarta.persistence.DiscriminatorValue;
    import jakarta.persistence.Entity;
    import jakarta.persistence.Temporal;
    import jakarta.persistence.TemporalType;
    
    @Entity
    @DiscriminatorValue(value = "CelularPrePago")
    public class CelularPrePago extends Celular {
        private double saldo;
        @Temporal(TemporalType.DATE)
        private Date dataDeValidade;
    }
    
  • Ligacao:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import java.util.Date;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import jakarta.persistence.Temporal;
    import jakarta.persistence.TemporalType;
    
    @Entity
    public class Ligacao {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
        @Temporal(TemporalType.TIMESTAMP)
        private Date data;
        private int duracao;
    }
    
  • Plano:
    
    package ubsocial.com.telefonia.model.entity.comercial;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    
    @Entity
    public class Plano {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
        private String nome;
        private double valorPorMinuto;
    }
    
  • pessoal/Cliente:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    import jakarta.persistence.DiscriminatorColumn;
    import jakarta.persistence.Entity;
    import jakarta.persistence.GeneratedValue;
    import jakarta.persistence.GenerationType;
    import jakarta.persistence.Id;
    import jakarta.persistence.Inheritance;
    import jakarta.persistence.InheritanceType;
    
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "class")
    @Entity
    public class Cliente {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;
        private String nome;
        private String endereco;
    }
    
  • pessoal/ClientePessoaFisica:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    import jakarta.persistence.DiscriminatorValue;
    import jakarta.persistence.Entity;
    
    @Entity
    @DiscriminatorValue(value = "ClientePessoaFisica")
    public class ClientePessoaFisica extends Cliente {
        private String cpf;
    }
    
  • cliente/PessoaJuridica:
    
    package ubsocial.com.telefonia.model.entity.pessoal;
    import jakarta.persistence.DiscriminatorValue;
    import jakarta.persistence.Entity;
    
    @Entity
    @DiscriminatorValue(value = "ClientePessoaJuridica")
    public class ClientePessoaJuridica extends Cliente {
        private String cnpj;
    }
    

Elaborado por Mateus Schwede
ubsocial.github.io