Desenvolvimento Back-end Java

Criando uma API com Spring Data JPA e Spring Web

Neste artigo vamos aprender como criar uma API com persistência de dados utilizando o Spring Web MVC e o Spring Data JPA.

3 meses atrás

Nos artigos anteriores conhecemos o projeto Spring Data e a framework Spring Data JPA, agora vamos ver na prática como utilizar as funcionalidades dessas ferramentas durante o desenvolvimento de uma Web API com as operações básicas de CRUD (Create, Read, Update e Delete).

Criação do projeto

HTTP - Fundamentos para desenvolvedores
Curso HTTP - Fundamentos para desenvolvedores
Conhecer o curso

Para o desenvolvimento da nossa API vamos utilizar algumas frameworks que compõem o ecossistema Spring, serão elas:

  • Spring Boot: Responsável por facilitar a criação de aplicação Spring prontas para produção e facilitar o processo de configuração do projeto.
  • Spring Web MVC: Responsável por toda a parte de web, logo será a framework responsável por lidar com as requisições e respostas da nossa aplicação.
  • Spring Data JPA: Responsável por facilitar a integração de nossa aplicação com a JPA, entregando assim um ambiente totalmente configurado e com abstrações que permitem um uso facilitado de toda a parte de persistência.

Agora que sabemos quais as ferramentas que iremos utilizar, vamos então partir para a criação do projeto. Para isso vamos até o site do Spring Initializr e então iremos definir as seguintes opções:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.6.3
  • Project Metadata:
    • Group: br.com.treinaweb
    • Artifact: api-spring-crud
    • Package name: br.com.treinaweb.apispringcrud
    • Packaging: Jar
    • Java: 17
  • Dependencies: Spring Web, Spring Data JPA e H2 Database

Veja que além do Spring Web e do Spring Data JPA também estou adicionando como dependência do projeto o H2 Database, o H2 Database é um banco de dados em memória o que facilita a nossa vida durante o desenvolvimento tendo em vista que não será necessário realizar nenhuma instalação ou configuração de um SGBD como o MySQL.

Com todas as opções preenchidas basta clicar no botão “GENERATE” que será realizado o download do projeto compactado, em seguida basta descompactar o arquivo e então abrir na IDE ou Editor de Código Fonte de sua preferência.

É importante que você possua o seu ambiente de desenvolvimento totalmente configurado para que consiga acompanhar esse artigo, caso não tenha o seu ambiente de desenvolvimento configurado basta acessar um dos artigos listados abaixo para ver como configurar o seu ambiente com base no seu sistema operacional:

Criando a entidade e o repositório

Vamos criar a entidade da aplicação, lembrando que uma entidade da JPA é basicamente uma classe no padrão Java Bean que irá representar a nossa tabela no banco de dados, onde cada atributo da classe será uma coluna na tabela, nesse exemplo iremos criar uma API para gerenciamento de clientes, então nada mais justo que criar uma entidade chamada Cliente.

Para melhor organização do código irei criar um pacote só para as entidades, o nome desse pacote será br.com.treinaweb.apispringcrud.entities e dentro desse pacote irei criar a classe Cliente, com o seguinte código:

package br.com.treinaweb.apispringcrud.entities;

import java.time.LocalDate;

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

@Entity
public class Cliente {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

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

    @Column(nullable = false)
    private LocalDate dataNascimento;

    public Cliente() {}

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

    public LocalDate getDataNascimento() {
        return dataNascimento;
    }

    public void setDataNascimento(LocalDate dataNascimento) {
        this.dataNascimento = dataNascimento;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Cliente other = (Cliente) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "Cliente [id=" + id + ", nome=" + nome + "]";
    }

}

No código acima temos a nossa entidade Cliente, a mesma possui os atributos id do tipo Longnome do tipo Stringemail do tipo String e dataNascimento do tipo LocalDate. Logo o Hibernate irá gerar uma tabela em nosso banco de dados com o mesmo nome da nossa classe contendo como coluna os atributos que foram definidos.

Agora vamos criar o nosso repository, lembrando que o repository é quem vai realizar as operações no banco de dados e esse repository é totalmente gerenciado pelo próprio Spring Data JPA. Para isso irei criar uma interface chamada ClienteRepository e essa interface irá estender a interface JpaRepository do Spring Data JPA, por questão de organização do código também irei criar um pacote chamado br.com.treinaweb.apispringcrud.repositories. O código de ClienteRepository ficará da seguinte maneira:

package br.com.treinaweb.apispringcrud.repositories;

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

import br.com.treinaweb.apispringcrud.entities.Cliente;

public interface ClienteRepository extends JpaRepository<Cliente, Long> {

}

Já temos a nossa entidade e o nosso repository criados, agora vamos desenvolver os endpoints da nossa API responsáveis pelas operações de CRUD.

Operações de CRUD em uma API com Spring Data JPA

APIs Rest - Fundamentos
Curso APIs Rest - Fundamentos
Conhecer o curso

Agora vamos criar a nossa camada de controller que será responsável por receber e tratar as requisições HTTP, para isso vou criar uma classe chamada ClienteController dentro do pacote br.com.treinaweb.apispringcrud.controllers que possuirá um atributo chamado clienteRepository do tipo ClienteRepository, esse atributo será injetado por injeção de dependências pelo próprio Spring e nesse momento o Spring Data JPA já irá prover de forma automática uma classe que implemente a nossa interface ClienteRepository, para realizar esse processo de injeção de dependências basta anotar o atributo com a anotação Autowired. Abaixo o código inicial do nosso controller.

package br.com.treinaweb.apispringcrud.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

}

Veja que também estamos utilizando as anotações RestController e RequestMapping no nível da classe, a anotação RestController serve para informar ao Spring Web MVC que essa será uma classe da camada controller e também será um controller REST, pois estamos desenvolvendo uma API. Já a anotação RequestMapping serve para informar qual a rota padrão para esse controller que no caso será a rota /api/v1/clientes.

Rota de cadastro de clientes

Vamos criar a nossa primeira rota que será a rota responsável por realizar o cadastro de novos clientes em nosso banco de dados, para isso vamos criar um método que será responsável por tratar a requisição, como queremos que essa requisição seja feita através de um verbo HTTP POST devemos anotar o nosso método com a anotação PostMapping.

Além disso, eu quero pegar as informações do cliente a ser cadastro no corpo da requisição, então iremos receber como parâmetro desse método uma instância da nossa classe Cliente e para informar ao Spring Web MVC que vamos receber os dados no corpo da requisição anotamos esse parâmetro com a anotação RequestBody.

E por fim ao finalizar todo o processamento de cadastro eu quero que o status code da resposta seja o 201, que significa que algum recurso foi criado na aplicação, para isso também iremos anotar o nosso método com a anotação ResponseStatus.

package br.com.treinaweb.apispringcrud.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import br.com.treinaweb.apispringcrud.entities.Cliente;
import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

    @PostMapping
    @ResponseStatus(code = HttpStatus.CREATED)
    public Cliente cadastrar(@RequestBody Cliente cliente) {
        return clienteRepository.save(cliente);
    }

}

Veja que utilizamos o método save do nosso repositório de cliente, esse é o método responsável por salvar os dados de uma entidade no banco de dados, então a única coisa que fizemos foi repassar os dados que recebemos na requisição diretamente para o método save do nosso repositório.

Com isso nossa rota de cadastro já está implementada.

Rota de listagem de clientes

Spring Framework - Fundamentos
Curso Spring Framework - Fundamentos
Conhecer o curso

Agora vamos implementar a rota que será responsável por listar todos os clientes cadastrados no banco de dados. Assim como na rota anterior vamos criar um método que irá tratar a requisição, porém dessa vez eu quero que a requisição seja realizada com o verbo HTTP GET, então utilizaremos agora a anotação GetMapping.

Dessa vez não será preciso utilizar a anotação ResponseStatus, pois eu quero que o status code seja o 200 e quando não definimos de forma explicita qual será o status code o Spring Web MVC retorna 200 por padrão.

E por fim como eu quero que a resposta dessa rota contenha em seu corpo uma lista de clientes o método deve retornar o tipo List.

package br.com.treinaweb.apispringcrud.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import br.com.treinaweb.apispringcrud.entities.Cliente;
import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

    @GetMapping
    public List<Cliente> listar() {
        return clienteRepository.findAll();
    }

    // Demais métodos omitidos para facilitação da leitura

}

Dessa vez utilizamos o método findAll do nosso repositório, ele fará justamente o que o próprio nome já diz, irá buscar e retornar todos os registros encontrados no banco de dados e o tipo do seu retorno é justamente um List.

Rota de busca de clientes por id

Agora vamos implementar a nossa rota responsável por realizar a busca de uma cliente por id, vamos precisar receber o id do cliente a ser buscado diretamente pela rota, para isso precisamos na anotação GetMapping passar um parâmetro que será a string /{id}, com isso estamos dizendo ao Spring Web MVC que o nosso método será executando quando for feita uma requisição com o verbo HTTP GET para a rota /api/v1/clientes/{id} e que esse {id} será um valor variável, além disso, precisamos que o método receba como parâmetro um dado do tipo Long que também chamaremos id e para informar ao Spring Web MVC que esse valor será preenchido com o que for colocado na parte variável da rota nós anotamos esse parâmetro com a anotação PathVariable.

Outro ponto é que nem sempre irá existir um cliente cadastrado com o id solicitado, então, iremos realizar uma verificação se foi encontrado algum cliente com o id passado na requisição, caso tenha sido encontrado vamos simplesmente retornar o cliente encontrado e caso contrário vamos retornar um erro com o status code 404, que significa que o recurso solicitado não foi encontrado.

package br.com.treinaweb.apispringcrud.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import br.com.treinaweb.apispringcrud.entities.Cliente;
import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

    @GetMapping("/{id}")
    public Cliente buscarPorId(@PathVariable Long id) {
        var clienteOptional = clienteRepository.findById(id);
        if (clienteOptional.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        return clienteOptional.get();
    }

    // Demais métodos omitidos para facilitação da leitura

}

Dessa vez utilizamos o método findById do nosso repositório, esse método realiza uma busca no banco de dados por algum registro que possua o id informado e retorna um Optional que contém a instância da nossa entidade caso alguém tenha sido encontrado ou caso não tenha encontrado ninguém esse Optional estará vazio.

Para podermos retornar o status code 404 no caso de nenhum cliente para o id informado ser encontrado, nós verificamos se retorno do método findById está vazio através do método isEmpty da própria classe Optional e em caso afirmativo lançamos uma exceção do tipo ResponseStatusException com o status code que queremos retornar.

Rota de exclusão de clientes por id

Spring Framework - Spring Data JPA
Curso Spring Framework - Spring Data JPA
Conhecer o curso

Agora vamos para a nossa rota de exclusão de clientes por id, essa rota será bem semelhante com a rota de busca de cliente por id, a diferença é que ao invés da requisição utilizar o verbo HTTP GET será utilizado o verbo HTTP DELETE, então iremos utilizar a anotação DeleteMapping.

Além disso, como não iremos retornar nenhum dado no corpo da resposta vamos informar para o Spring Web MVC que ao término da execução do método de exclusão iremos retornar o status code 204, que significa que a operação foi realizada com sucesso, porém não tem nada a ser exibido.

package br.com.treinaweb.apispringcrud.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import br.com.treinaweb.apispringcrud.entities.Cliente;
import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

    @DeleteMapping("/{id}")
    @ResponseStatus(code = HttpStatus.NO_CONTENT)
    public void excluirPorId(@PathVariable Long id) {
        var clienteOptional = clienteRepository.findById(id);
        if (clienteOptional.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        clienteRepository.delete(clienteOptional.get());
    }

    // Demais métodos omitidos para facilitação da leitura

}

Dessa vez nós utilizamos dois métodos do nosso repositório, primeiro utilizamos o findById para verificar se existe algum cliente cadastrado com o id em questão e por fim utilizamos o método delete passando o cliente encontrado. O método delete recebe uma instância da nossa entidade e então realiza a sua exclusão do banco de dados.

Rota de atualização de clientes

Agora para finalizarmos as nossas operações de CRUD vamos implementar a última rota da nossa API, rota essa que será responsável por realizar a atualização de um cliente no banco de dados. Essa rota será bem semelhante às rotas de buscar por id e excluir por id, a diferença será que vamos utilizar o verbo HTTP PUT, logo iremos anotar o nosso método com a anotação PutMapping e também vamos receber uma instância da classe Cliente com os novos dados a serem alterados.

package br.com.treinaweb.apispringcrud.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import br.com.treinaweb.apispringcrud.entities.Cliente;
import br.com.treinaweb.apispringcrud.repositories.ClienteRepository;

@RestController
@RequestMapping("/api/v1/clientes")
public class ClienteController {

    @Autowired
    private ClienteRepository clienteRepository;

    @PutMapping("/{id}")
    public Cliente atualizarPorId(@PathVariable Long id, @RequestBody Cliente cliente) {
        var clienteOptional = clienteRepository.findById(id);
        if (clienteOptional.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        cliente.setId(id);
        return clienteRepository.save(cliente);
    }

    // Demais métodos omitidos para facilitação da leitura

}

Veja que nessa rota de exclusão nós utilizamos o método findById apenas para garantir que existe um cliente cadastrado com o id solicitado e então utilizamos novamente o método save para realizar a atualização do cliente, e sim o método save pode ser utilizado tanto para cadastro quanto para atualização dos dados no banco de dados, para que o Spring Data JPA saiba se deve realizar uma atualização ou um cadastro ele basicamente verifica o id da entidade que está sendo salva, logo, se a entidade que está sendo salva possui um id será uma atualização se a entidade não possuir um id será um cadastro.

Testando a nossa API

Spring Framework - Desenvolvimento de APIs REST
Curso Spring Framework - Desenvolvimento de APIs REST
Conhecer o curso

Agora que temos todas as rotas que realizam as operações de CRUD em nossa API vamos testar para ver se está tudo funcionando corretamente. Para realizar esses testes precisamos de algum programa que consiga realizar requisições HTTP, eu particularmente gosto de utilizar o Insomnia, porém se você possui preferência por outro programa fique à vontade para utilizá-lo.

Primeiro precisamos executar a nossa aplicação, isso é bastante simples, basta abrir o terminal na pasta do projeto, no mesmo nível do arquivo pom.xml e então executar o seguinte comando:

mvn spring-boot:run

Após esperar alguns segundos para a execução do projeto você verá uma mensagem parecida com a seguinte:

2022-01-24 08:28:45.919  INFO 2391 --- [  restartedMain] b.c.t.a.ApiSpringCrudApplication: Started ApiSpringCrudApplication in 1.757 seconds (JVM running for 1.973)

Isso significa dizer que aplicação executou com sucesso e já podemos realizar requisições para a nossa API.

Primeiro vamos realizar um cadastro, para isso vamos enviar uma requisição com o verbo HTTP POST para a url http://localhost:8080/api/v1/clientes e no corpo da requisição iremos colocar os dados do cliente a ser cadastrado.

Agora vamos realizar a listagem de todos os clientes cadastrados, para isso basta enviar uma requisição com o verbo HTTP GET para a url http://localhost:8080/api/v1/clientes.

Vamos testar também a rota de busca de cliente por id, para isso precisamos enviar uma requisição com o verbo HTTP GET para a url http://localhost:8080/api/v1/clientes/1.

Agora vamos testar a rota de atualização por id, para isso vamos enviar uma requisição com o verbo HTTP PUT para url http://localhost:8080/api/v1/clientes/1 e no corpo da requisição enviar os novos dados a serem atualizados.

E por fim vamos testar a nossa rota de exclusão por id, para isso vamos enviar uma requisição com o verbo HTTP DELETE para a url http://localhost:8080/api/v1/clientes/1.

E com isso finalizamos os nossos testes, a API está funcionando perfeitamente.

Conclusão

Caso queira ver o código-fonte do projeto desenvolvido nesse artigo ele está disponível nesse repositório do GitHub.

Durante esse artigo vimos como podemos utilizar os métodos disponibilizados pelos repositórios do Spring Data JPA na prática durante o desenvolvimento de uma API.

Falamos bastante coisa do Spring Data JPA, mas isso tudo é só a ponta do iceberg, existem muitos outros recursos a serem explorados, aqui na TreinaWeb nós temos o curso Spring Framework - Spring Data JPA que possui 03h24min de vídeo e um total 18 exercicios. Conheça também nossos outros cursos de Spring.

Veja quais são os tópicos abordados durante o curso de Spring Data JPA:

  • Adicionar o Spring Data JPA em um projeto Spring Boot
  • Entender o Respository Pattern do Spring Data JPA
  • Mapear classes de entidade com a JPA
  • Entender o que o que é e como implementar um CRUD com relacionamentos 1-1
  • Entender o que o que é e como implementar um CRUD com relacionamentos 1-N
  • Entender o que o que é e como implementar um CRUD com relacionamentos N-N
  • Realizar buscas personalizadas com a anotação @Query e as Keywords do Spring Data JPA

Autor(a) do artigo

Cleyson Lima
Cleyson Lima

Professor, programador, fã de One Piece e finge saber cozinhar. Cleyson é graduando em Licenciatura em Informática pelo IFPI - Campus Teresina Zona Sul, nos anos de 2019 e 2020 esteve envolvido em vários projetos coordenados pela secretaria municipal de educação da cidade de Teresina, onde o foco era introduzir alunos da rede pública no mundo da programação e robótica. Hoje é instrutor dos cursos de Spring na TreinaWeb, mas diz que seu coração sempre pertencerá ao Python.

Todos os artigos

Artigos relacionados Ver todos