Skip to content

JonathanWallace/Tutorial-Java-Spring-Boot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

✅ Criando uma Aplicação Backend com Spring Boot (Java)

Este tutorial guia você na criação de uma aplicação backend do zero utilizando Spring Boot, Java, Maven e o banco de dados Oracle, além de boas práticas como DTOs, validações, tratamento global de exceções e organização modular.


1. Inicializando o Projeto com Spring Initializr

Acesse: https://start.spring.io

Configurações sugeridas:

  • Project: Maven
  • Language: Java
  • Spring Boot: Versão LTS (ex: 3.2.x)
  • Group: com.seuprojeto
  • Artifact: nomedoprojeto
  • Name: nomedoprojeto
  • Description: Uma breve descrição da aplicação
  • Package name: com.seuprojeto.nomedoprojeto
  • Packaging: Jar
  • Java Version: 17 ou superior

Dependências recomendadas:

  • Spring Web
  • Spring Data JPA
  • Validation
  • Lombok
  • Flyway Migration
  • Oracle Driver
  • Spring Security

Clique em Generate para baixar o projeto.

Anotações usadas

  • @SpringBootApplication: adicionada automaticamente na classe principal, combina @Configuration, @EnableAutoConfiguration e @ComponentScan.

2. Importando o Projeto no IntelliJ IDEA

  1. Extraia o .zip baixado.
  2. Abra o IntelliJ IDEA.
  3. Vá em File > Open e selecione a pasta extraída.
  4. O IntelliJ reconhecerá o projeto como Maven automaticamente.

3. Estrutura Recomendada de Pacotes

Dentro de src/main/java/com/seuprojeto/nomedoprojeto, crie:

  • controller — Controladores REST (entrada da API)
  • model ou domain — Entidades JPA
  • repository — Interfaces que herdam de JpaRepository
  • service — Regras de negócio
  • dto — Objetos de transferência de dados (entrada/saída)
  • exception — Classes de exceção customizadas
  • advice — Manipuladores globais de erro

4. Criando uma Entidade (Model)

@Entity
@Table(name = "usuarios")
public class Usuario {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "usuario_seq")
    @SequenceGenerator(name = "usuario_seq", sequenceName = "usuario_seq", allocationSize = 1)
    private Long id;

    @Column(nullable = false, length = 100)
    private String nome;

    @Column(nullable = false, unique = true)
    private String email;

    public Usuario() {}

    public Usuario(Long id, String nome, String email) {
        this.id = id;
        this.nome = nome;
        this.email = email;
    }

    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 getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Usuario)) return false;
        Usuario usuario = (Usuario) o;
        return Objects.equals(id, usuario.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

Anotações usadas:

  • @Entity: indica que a classe é uma entidade JPA.
  • @Table: define o nome da tabela no banco.
  • @Id: define o campo como chave primária.
  • @GeneratedValue: gera o valor da chave automaticamente.
  • @SequenceGenerator: usa sequência no banco para gerar o ID.
  • @Column: configura propriedades da coluna (nullable, length, unique).

5. Criando o Repositório

public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
    List<Usuario> findByNome(String nome);
    Optional<Usuario> findByEmail(String email);

    @Query("SELECT u FROM Usuario u WHERE u.email = :email")
    Optional<Usuario> buscarPorEmailJPQL(@Param("email") String email);
}

Anotações e interfaces:

  • JpaRepository: interface do Spring Data que oferece métodos prontos como save, findAll, deleteById etc.
  • @Query: permite escrever consultas personalizadas em JPQL.
  • @Param: vincula o nome do parâmetro na query ao parâmetro do método.

Exemplos de métodos com convenções do Spring Data:

  • findByNome(String nome)
  • findByEmail(String email)
  • findByNomeContaining(String termo) — busca parcial
  • findByEmailAndNome(String email, String nome) — múltiplas condições
  • existsByEmail(String email) — verifica existência
  • countByNome(String nome) — retorna a contagem

6. Criando a Camada de Serviço

@Service
public class UsuarioService {

    @Autowired
    private UsuarioRepository usuarioRepository;

    public Usuario salvar(Usuario usuario) {
        return usuarioRepository.save(usuario);
    }

    public List<UsuarioDTO> listarTodos() {
        return usuarioRepository.findAll()
            .stream()
            .map(usuario -> new UsuarioDTO(usuario.getNome(), usuario.getEmail()))
            .toList();
    }

    public Page<UsuarioDTO> listarPaginado(Pageable pageable) {
        return usuarioRepository.findAll(pageable)
            .map(usuario -> new UsuarioDTO(usuario.getNome(), usuario.getEmail()));
    }

    public Usuario buscarPorEmail(String email) {
        return usuarioRepository.findByEmail(email)
            .orElseThrow(() -> new RecursoNaoEncontradoException("Usuário com e-mail " + email + " não encontrado"));
    }

    public void deletarPorId(Long id) {
        usuarioRepository.deleteById(id);
    }
}

Anotações usadas:

  • @Service: define a classe como componente de serviço (regra de negócio).
  • @Autowired: injeta automaticamente dependências gerenciadas pelo Spring.

Sobre paginação:

  • O retorno Page<UsuarioDTO> permite paginação automática pela interface do Spring Data.
  • A resposta inclui: content, totalElements, totalPages, number (página atual) etc.
  • Exemplos de uso na URL:
    • ?page=0&size=10 — primeira página com 10 elementos
    • ?sort=nome,asc — ordena por nome de forma crescente

7. Criando o Controller REST

@RestController
@RequestMapping("/usuarios")
public class UsuarioController {

    @Autowired
    private UsuarioService usuarioService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Usuario criar(@RequestBody @Valid UsuarioDTO dto) {
        Usuario usuario = new Usuario(null, dto.getNome(), dto.getEmail());
        return usuarioService.salvar(usuario);
    }

    @GetMapping
    @ResponseStatus(HttpStatus.OK)
    public List<Usuario> listar() {
        return usuarioService.listarTodos();
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deletar(@PathVariable Long id) {
        usuarioService.deletarPorId(id);
    }
}

Anotações usadas:

  • @RestController: define a classe como controller REST.
  • @RequestMapping: define o path base do controller.
  • @PostMapping, @GetMapping, @DeleteMapping: mapeiam requisições HTTP.
  • @RequestBody: indica que o corpo da requisição deve ser mapeado para o parâmetro.
  • @Valid: ativa validações no DTO.
  • @ResponseStatus: define o código de status da resposta.
  • @PathVariable: extrai valores da URL.

#Crie uma sessão explicando a diferença entre Mappings com @PathVariable e @PathParam e seus casos de uso (seja breve)#

8. Utilizando Flyway para Migrations

Crie a pasta src/main/resources/db/migration

Exemplo de migration:

V1__criar_tabela_usuarios.sql

CREATE SEQUENCE usuario_seq START WITH 1 INCREMENT BY 1;

CREATE TABLE usuarios (
    id NUMBER PRIMARY KEY,
    nome VARCHAR2(100) NOT NULL,
    email VARCHAR2(150) NOT NULL UNIQUE
);

🔁 Siga o padrão V{versao}__descricao.sql, como V2__adicionar_coluna_status.sql


9. application.properties (Oracle + Flyway)

spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=usuario
spring.datasource.password=senha
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration

# Exibir erros no response
server.error.include-message=always
server.error.include-binding-errors=always
server.error.include-stacktrace=always

10. Criando DTOs e Validações

public class UsuarioDTO {

    @NotBlank(message = "Nome é obrigatório")
    private String nome;

    @NotBlank(message = "Email é obrigatório")
    @Email(message = "Email inválido")
    private String email;

    // Getters e setters
}

Anotações usadas:

  • @NotBlank: valida que o campo não é nulo nem vazio.
  • @Email: valida que o campo tem formato de email válido.
  • @Size: valida a quantidade de caracteres com o uso dos parametros min e max.

11. Tratamento Global de Erros com @RestControllerAdvice

Exceção personalizada:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class RecursoNaoEncontradoException extends RuntimeException {
    public RecursoNaoEncontradoException(String message) {
        super(message);
    }
}

Handler global:

@RestControllerAdvice
public class ApplicationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<List<String>> handleValidacoes(MethodArgumentNotValidException ex) {
        List<String> erros = ex.getBindingResult().getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .toList();
        return ResponseEntity.badRequest().body(erros);
    }

    @ExceptionHandler(RecursoNaoEncontradoException.class)
    public ResponseEntity<String> handleNotFound(RecursoNaoEncontradoException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body("Erro inesperado: " + ex.getMessage());
    }
}

Anotações usadas:

  • @RestControllerAdvice: intercepta e trata exceções globalmente para todos os controllers.
  • @ExceptionHandler: define o tratamento de uma exceção específica.
  • @ResponseStatus: define o código de status HTTP a ser retornado.

12. Testando a API com Insomnia ou Postman

Endpoints disponíveis:

  • GET /usuarios — Lista todos os usuários
  • POST /usuarios — Cria um novo usuário
  • DELETE /usuarios/{id} — Deleta um usuário por ID

Exemplo de JSON para POST:

{
  "nome": "João da Silva",
  "email": "joao@email.com"
}

-- 13. Autenticação e Autorização Ao instalar as dependencis do Spring Security, ao inicializar a aplicação, agora precisaremos nos autenticar para acessar os endpoints. Por padrão no Spring Security usamos o "user" como usuário e a senha é uma combinação hexadecimal gerada ao iniciar a aplicação e pode ser obtida no console. (Importante: esses usuários e senhas são somente para produção!) Se for necessário para o ambiente de desenvolvimento, no arquivo application.properties é possível definir o usuário e senha para facilitar o uso, através das váriáveis: spring.security.user.name=jonathan spring.security.user.password=123456

A autenticação pode ser via Basic Auth (Stateful) (que é o padrão do Spring Boot) que usa "user" e "password" ou pode ser via JWT (JSON Web Token) (Stateless) que é através de um token criptografado composto por três partes: Header, Payload e Verify Signatura. Importante: precisa adicionar a dependencia do JWT, pode ser obtida no site da JWT. Explicar o uso de cada parte do JWT

Criar a entidade de usuário no diretório Model(ou Domain) com nome, email, senha, role, entre outras propriedades, bem como seus getters, setters, equals e hashCode. Muito importate: implements UserDetails na entidade usuario e implemente os métodos da interface. Exemplo:

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(this.role == UsuarioRole.ADMIN){
            return List.of(
                    new SimpleGrantedAuthority("ROLE_ADMIN"),
                    new SimpleGrantedAuthority("ROLE_USER")
            );
        } else if (this.role == UsuarioRole.USER){
            return List.of(
                    new SimpleGrantedAuthority("ROLE_USER")
            );
        }
        return List.of();
    }

    @Override
    public String getPassword() {
        return senha;
    }

    @Override
    public String getUsername() {
        return email;
    }

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

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

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

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

Criar o Enum para UserRole (os papeis do usuário para autorização, por exemplo: ADMIN(para acesso geral) e USER(para acesso básico), entre outros). Criar os DTOs de cadastro e exibição do usuário (no exibição por exemplo não retornar a senha) Criar a Sequence para o ID do usuário e a tabela tbl_usuarios na migração do FlyWay. Configurar a model do usuário para implementar a UserDetails (do package: "org.springframework.security.core.userdetails.UserDetails"). Importante: Não esquecer de implementar os métodos da UserDetails e configura-los. Exemplo de implementação do método public Collection<? extends GrantedAuthority> getAuthorities():

if(this.role == UsuarioRole.ADMIN){
            return List.of(
                    new SimpleGrantedAuthority("ROLE_ADMIN"),
                    new SimpleGrantedAuthority("ROLE_USER")
            );
        } else if (this.role == UsuarioRole.USER){
            return List.of(
                    new SimpleGrantedAuthority("ROLE_USER")
            );
        }
        return List.of();

Criar o repository do usuário que extends a JpaRepository<Usuario, Long> Criar o método public findByEmail(String email). Criar o service do usuario com os métodos de gravarUsuario (não esquecendo de encriptografar a senha), buscarUsuarioPorId, etc. Criar o service de authorization que implementa o UserDetailsService (do package: org.springframework.security.core.userdetails.UserDetailsService)

Criar os diretórios "config.security" e criar o "SecurityConfig" com as seguintes implementações:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filtrarCadeiaDeSeguranca(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .csrf(csrf -> csrf.disable())
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(HttpMethod.GET, "/api/contatos").permitAll()
                        .requestMatchers(HttpMethod.PUT, "/api/contatos").hasRole("ADMIN")
                        .requestMatchers(HttpMethod.POST,"/api/contatos").hasRole("ADMIN")
                        .anyRequest().authenticated())
                .build();
    }
}

E criar também o gerenciador de autenticação dentro da classe SecurityConfig que será usado nos controladores de autenticação (AuthController)

  @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

E não esquecer de criar o PasswordEncoder:

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

Caso na hora de cadastrar um novo usuario, exista um conflito de integridade de dados do banco de dados como por exemplo tentar cadastrar um email já utilizado, será necessario lidar com esse erro dentro da classe ApplicationExceptionHandler:

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler(DataIntegrityViolationException.class)
    public Map<String, String> manusearIntegridadeDosDados() {
        Map<String, String> mapaDeErro = new HashMap<>();
        mapaDeErro.put("erro", "Email já está cadastrado.");
        return mapaDeErro;
    }

Criar os endpoints de Autenticação com através de um controller (AuthController) no diretório de controles e cadastrar o endpoints, por exemplo:

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UsuarioService usuarioService;

    @PostMapping("/login")
    public ResponseEntity login(@RequestBody @Valid UsuarioLoginDto usuarioLoginDto) {
        UsernamePasswordAuthenticationToken usernamePassword = new UsernamePasswordAuthenticationToken(usuarioLoginDto.email(), usuarioLoginDto.senha());
        Authentication auth = authenticationManager.authenticate(usernamePassword);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/register")
    @ResponseStatus(HttpStatus.CREATED)
    public UsuarioExibicaoDto register(@RequestBody @Valid UsuarioCadastroDto usuarioCadastroDto) {
        return usuarioService.gravarUsuario(usuarioCadastroDto);
    }
}

Criar em "config.security" a classe TokenService para será responsável por gerar e validar os tokens e criar a palavraSecreta (que será usado para criação do token) em variável de ambiente ou no application.properties (exemplo: minha.chave.secreta=${JWT.SECRET:fiap})

@Service
public class TokenService {

    @Value("${minha.chave.secreta}")
    private String palavraSecreta;

    public String gerarToken(Usuario usuario) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(palavraSecreta);
            String token = JWT
                    .create()
                    .withIssuer("contatos")
                    .withSubject(usuario.getEmail())
                    .withExpiresAt(gerarDataDeExpiracao())
                    .sign(algorithm);
            return token;
        } catch (JWTCreationException exception) {
            throw new RuntimeException("Não foi possível gerar o token.");
        }
    }

    public String validarToken(String token) {
        try{
            Algorithm algorithm = Algorithm.HMAC256(palavraSecreta);
            return JWT.require(algorithm)
                    .withIssuer("contatos")
                    .build()
                    .verify(token)
                    .getSubject();
        } catch (JWTVerificationException exception) {
            return "";
        }
    }

    private Instant gerarDataDeExpiracao() {
        return LocalDateTime.now().plusHours(2).toInstant(ZoneOffset.of("-03:00"));
    }
}

Criar a classe VerificarToken:

@Component
public class VerificarToken extends OncePerRequestFilter {
    @Autowired
    private TokenService tokenService;

    @Autowired
    private UsuarioRepository usuarioRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorizationHeader = request.getHeader("Authorization");
        String token = "";

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            token = authorizationHeader.substring(7);
            String login = tokenService.validarToken(token);
            UserDetails usuario = usuarioRepository.findByEmail(login);
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(usuario, null, usuario.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        } else {
            token = null;
        }

        filterChain.doFilter(request, response);
    }
}

Explicar cada anotação e cada método usado no return

✅ Considerações Finais

  • Utilize DTOs para manter as entidades desacopladas da entrada/saída da API.
  • Sempre valide entradas com @Valid.
  • Crie exceções específicas e trate-as globalmente.
  • Mantenha o projeto modular e bem estruturado.
  • Use migrations com Flyway para versionar seu banco de dados.
  • Implemente equals() e hashCode() corretamente com base no ID da entidade.

Ficou com dúvidas ou quer expandir este projeto com autenticação JWT, testes, Swagger, entre outros? Me avise! 🚀

About

Tutorial de Java Spring Boot

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published