Aplicações Backend em Java -Desenvolvendo CRUD de forma simples"
- #Spring
- #PostgreSQL
- #Java
Neste Artigo, estaremos desenvolvendo uma aplicação do "zero" em linguagem Java, e tendo como objetivo principal fazer um “CRUD” de forma mais simples e explicativa possível, porém com uma arquitetura e código limpo.
Fundamentos de um CRUD
Primeiramente, o que é um CRUD? Explicando de maneira simplificada, um CRUD é basicamente as quatro operações básicas que uma aplicação deve-se fazer em um sistema com gerenciamento de banco de dados, essas quatro operações são:
- Create: Com o objetivo de criar e armazenar novos registros nesse banco de dados.
- Read: Operação responsável por recuperar e consultar as informações existentes no banco.
- Update: Responsável por atualizar ou modificar os dados já existentes no banco de dados.
- Delete: Refere-se em deletar registros existentes no banco de dados.
Ambiente de desenvolvimento
Para desenvolvermos essa aplicação, serão necessárias algumas ferramentas instaladas em sua máquina, aqui está uma lista detalhada do que será necessário:
- Java JDK17 instalado
- IDE compatível com Java (Exemplos com a IDE IntelJ)
- Docker Desktop instalado
- Gerenciador de Banco de dados instalado (Exemplos com DBeaver)
- Postaman (Para testes dos "endpoints")
Implementação do projeto
Para iniciarmos esse projeto, utilizaremos uma ferramenta online que facilita a iniciação de novos projetos de forma simples e rápida. Acessando o site https://start.spring.io/ utilizaremos as configurações de projeto Maven, com linguagem Java e utilizando a versão do Spring Boot em 3.2.1 para esse projeto, além de preenchermos os "Metadata" conforme necessidades.
Figura 1 - Início do projeto
Além dessas informações, iremos adicionar ao lado direito, as dependências que serão usadas nesse projeto:
- Spring Web
- Spring Data JPA
- Lombok
- PostgresSQL Drive
Dessa forma:
Figura 2 - Dependências do projeto
Clicando em "Generate", logo abaixo, podemos fazer o download do “esboço” do projeto onde iremos criar a aplicação. Após isso, descompactar o arquivo, e abrir a pasta na IDE que será utilizada, nesse caso, na IDE IntelJ, e ficar dessa forma:
Figura 3 - Projeto iniciado
Agora, com o projeto iniciado, utilizaremos uma estrutura MVC, portanto será necessário criar seis package dentro da pasta do projeto, essas pastas serão:
- Model: Onde ficarão todas as nossas Entidades.
- DTO: Nesse package definimos as DTOs das entidades.
- Controllers: Onde ficaram as classes responsáveis pelas rotas.
- Services: Aqui ficam as classes responsáveis por fazer a regra de negócios dos nossos objetos.
- Repositories: As interfaces que irão acessar o banco de dados com o JPA.
- Config: Um packege de configurações para componentes utilizados no projeto
O projeto ficara com uma arquitetura bem distribuída e funcional, da forma que está separado, o projeto deve ficar dessa forma:
Figura 4 - Arquitetura do projeto
Iniciamos criando uma classe no package Model, com nome de People, essa classe será uma entidade do objeto People, esse objeto irá ter os atributos, ID, name, lastName e age. Além disso utilizaremos nessa classe quatro ‘anotations’ essenciais para o funcionamento da aplicação:
- @Data – Uma anotação da biblioteca do Lomboak que foi adicionada no inicio do projeto, tem como objetivo eliminar a necessidade de escrever os métodos getters, setters, toString, equals, e hashCode. Essa anotação adiciona esses métodos automaticamente durante a compilação do código, tornando mais limpo.
- @Entity – Uma anotação do JPA, outra dependência utilizada no inicio do projeto, tem como objetivo definir essa classe como uma entidade persistente, ou seja, um objeto que será persistido no banco de dados relacional.
- @Tabla – Essa anotação é usada em conjunto com a @Entity para adicionar detalhes sobre a tabela associada a esse objeto no banco de dados, nomeando o nome da tabela onde esse objeto será persistido.
- JsonInclude – Essa anotação ajuda a controlar a inclusão/exclusão de propriedades nulas ou vazias do JSON relacionado a essa entidade.
Depois de criar a Entidade/Classe People, teremos uma classe dessa forma:
Figura 5 - Classe ou Entidade People
No “package” DTO, teremos a classe PeopleDTO, que é muito comum em muitos projetos, esse DTO, refere-se ao “Data Transfer Object”, que por boas praticas utiliza-se basicamente para separar a representação interna dos objetos com o que está sendo transferido com outras classes e até mesmo banco de dados.
Essa classe, basicamente uma “cópia” da entidade, porém pode-se restringir a quais atributos você vai transferir, por exemplo, se eu não posso fornecer todos os dados daquela entidade para um certo método ou serviço, eu faço uma classe DTO apenas com os atributos que eu gostaria que fosse transferido, basicamente é isso!
No caso desse projeto, vou utilizar a DTO por boa pratica, pois minha DTO estará idêntica a minha Entidade, ou Model, como preferir. Essa classe terá a ‘anotation’ @Data, apenas ela, que tem como objetivo deixar o código mais limpo, e mais fácil de se ler.
Segue o exemplo de como ficará a classe PeopleDTO:
Figura 6 - Classe PeopleDTO
Agora antes de começarmos os packages Controllers, Services e Repositories, deve-se fazer as configurações do projeto. No package Config, cria-se uma classe chamada Config e nela utilizaremos a anotation @Configuration, para indicar para nossa aplicação essa classe é uma classe de configuração.
Nessa classe, criaremos uma Bean para iniciarmos o ModelMapper, para auxiliar na transição de um tipo de objeto para outro, como veremos mais à frente.
Exemplo da classe “Config” com o método criado (Bean):
Figura 7 - Classe de configuração
Enfim com as configurações criadas, o próximo passo, será implementar a interface no package Repositories. Basicamente essa interface irá “conversar” com o banco de dados, e essa única interface será o suficiente para atender todos os métodos que estarão no package Service. Essa interface deverá ser dessa forma:
Figura 8 - Interface na camada Repository
Agora, com a interface pronta, vamos fazer uma abordagem diferente, sabe-se que quando fazermos uma chamada na "Api", essa chamada vai chegar diretamente na Controller, e esse método, fará a regra de negócio do programa, na camada Service.
A Controller terá básicos cinco métodos:
- findAll – Responsável por buscar uma lista com todos os dados da tabela salva no banco de dados.
- findById – Método que buscará um objeto “pessoa” a partir de um id especifico.
- Insert – Método que tem como objetivo salvar o objeto no banco de dados.
- Update – Responsavel por atualizar um objeto já existente no banco de dados.
- Delete – Método que deletará um objeto no banco de dados com ID especifico.
Na camada “Controller”, haverá uma classe chamada PeopleController, e na camada “Service”, uma classe chamada PeopleService, ficando as duas classes respectivamente dessa forma:
Figura 9 - Classe PeopleController
Figura 10 - Classe PeopleService
Tentando ser um pouco mais especifico, farei uma breve explicação de cada método na camada “Controller“, e ao mesmo tempo tentando explicar a continuação dele na camada “Service”, começando pelos métodos FindAll e FindByID:
- FindAll:
Figura 11 - Controller - Método FindAll
Após chegar à requisição na camada “controller” onde é chamado esse método, é criado uma Lista de objetos PessoaDTO e essa lista é preenchida ao chamar o método findAll na camada “Service, que estará dessa forma:
Figura 12 - Service - Método FindAll
Esse método chama a interface da “repository” e então recebe uma lista de um objeto People, que logo em seguida, transforma esse objeto em PeopleDTO e retorna esse objeto para a “controller”. Após isso, o método na “controller” retorna para a requisição uma lista desse objeto PeopleDTO.
- FindByID:
Figura 13 - Controller - Método FindByID
Esse método, tem o mesmo principio básico que o método “findAll”, porém é passado um “argumento ID” na rota {id}, com esse ID, o busca-se o método na “service”.
Figura 14 - Service - Método FindByID
No método findByID, dentro da camada “service”, o método busca novamente a interface de camada “repository”, e retorna para a camada “controller” um único objeto PeopleDTO com ID específico. E por fim, o método retorna um Objeto PeopleDTO na requisição.
- Insert:
O método “insert”, já não é um método GET, e sim um método POST, esse método recebe um Body da requisição, onde deve-se conter um JSON, representando um objeto PeopleDTO. Ao receber esse “body”, o método envia-o como argumento para outro método na “service”, ficando dessa forma:
Figura 15 - Controller - Método Insert
No método da “service” (Método abaixo), ele transforma esse objeto em um objeto People e passa como argumento para a interface onde vai salvar esse objeto no banco de dados, e depois disso retorna para a camada “controller” o objeto que foi persistido no banco, dessa forma como segue o exemplo:
Figura 16 - Service - Método Insert
Finalizando essa requisição, o método na camada “controller” retorna o objeto persistido.
- Update:
Esse é o método do tipo PUT, e responsável por atualizar um objeto já existente, portanto, o JSON que deverá ser enviado no body, deve-se conter o ID do objeto salvo no banco para atualizar o mesmo.
Esse método deve-se seguir com os mesmos princípios dos outros, recebe a requisição na controller, chama a service para fazer as devidas conversões, passa para a repository para acessar ao banco e depois devolve o objeto conforme as necessidades de cada método. Segue o exemplo na camada “controller”:
Figura 17 - Controller - Método Update
Como já explicado, o método acessa a camada “service”, ficando dessa forma:
Figura 18 - Service - Método Update
Nessa camada “service”, como já explicado, antes de mais nada, busca-se o objeto apartir do método this.findById(id), passando o ID do body como argumento, e logo após, transforma-se esse objeto PeopleDTO em objeto People, para poder enviar para o método this.insert(peoplePersisted) e salvar no banco de dados. E depois disso segue o fluxo enviando novamente para a camada “controller” e finalizando a requisição.
- Delete:
O último método, é o mais “simples”, basicamente a recebe-se da rota um ID, e esse ID é passado como argumento na camada para o método delete() na camada “service”, dessa forma:
Figura 19 - Controller - Método Delete
Na camada “service”, é apenas chamado a “repository”, com argumento ID novamente, e a interface é responsável em deletar o objeto com o id específico.
Figura 20 - Service - Método Delete
Apenas um detalhe, método na camada “service” é um método void, portanto, não há retorno, e na camada “controller”, retorna um ResponseEntity<Void>.
Com todos os métodos prontos, vamos ter que fazer algumas configurações no arquivo “application.properties” do projeto, nesse arquivo, será definido várias propriedades do projeto, e deverá estar dessa maneira:
Figura 21 - Código no application.properties
Descrevendo superficialmente o que cada linha é responsável, obtemos isso:
- server.port=8090 – Significa que a aplicação utilizara a porta 8090.
- spring.datasource.url=jdbc:postgresql://localhost:5432/postgres-database – A “url” de conecção com o banco de dados que será utilizado.
- spring.datasource.username=postgres – O nome de usuário para conectar ao banco de dados.
- spring.datasource.password=postgres – O password para conectar do banco.
- spring.datasource.driverClassName=org.postgresql.Driver – Especifica a classe do drive JDBC para conectar com o banco de dados.
- spring.sql.init.platform=postgresql – Especifica a plataforma utilizada para os ”scripts” de inicialização SQL.
- spring.sql.init.mode=Always – Essa linha garante que toda inicialização da aplicação os “scripts” serão executados.
- spring.jpa.hibernate.ddl-auto=update – Garante que o “Hibernate” sempre atualizem os “schemas” no banco de dados com base nas classes e entidades do projeto.
Com esse código, é possível fazer alguns exemplos para “testarmos” essa aplicação.
Implementando o banco de dados
Com o Docker Desktop instalado, utilizaremos um comando no terminal para criar um container a partir de uma imagem do banco de dados PostgreSQL, e utilizarmos esse container para salvar os dados do projeto. Esse comando, irá gerar esse container e definirá as variáveis existentes, ficando dessa forma:
docker run -d --name database-postgres -e POSTGRES_DB=postgres-database -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres
Utilizando esse comando, o contêiner com banco postgres será criado, e com as variáveis passadas no ”docker run” ficará dessa forma:
- Nome do container: “database-postgres”,
- Nome do banco de dados: “postgres-database”
- Nome de usuário: “postgres”
- Password: “postgres”
- Porta do container mapeada para o host: “5432”
Com o banco de dados criado, pode-se iniciar a aplicação e a própria aplicação mapeara o banco de dados conforme a/as classes/entidades do projeto.
Para iniciar a aplicação, depois de tudo configurado, utilizando o “IntliJ” basta clicar no atalho “Run” ou utilizar o atalho “Shift + F10”, dessa forma:
Figura 22 - Iniciando a aplicação com IntliJ
Após conseguir rodar a aplicação, pode-se utilizar uma ferramenta de administração e desenvolvimento de banco de dados, nesse caso, os exemplos serão pelo DBeaver. Utilizando o DBeaver, pode-se acessar a tabela criada no banco, e deve estar dessa forma, pronta para uso:
Figura 23 - Diagrama no banco de dados
Com o Banco de dados “okay”, utilizaremos uma outra ferramenta, para auxiliar a fazermos testes na pratica, essa ferramenta chama-se Postman, e com ela, poderemos fazer chamadas em nossa aplicação para testar o funcionamento do CRUD.
O primeiro método a ser testado, será o método INSERT, pois ainda não temos nenhum dado no banco. Esse método, é um método POST, e com ele, enviaremos um JSON no body. Esse JSON nada mais é que a representação de um objeto “PeolpleDTO” na aplicação. Os JSONs utilizados serão todos parecidos com esse:
Figura 24 - Json teste para o método INSERT
Com esse JSON e com a aplicação funcionando, faremos três chamadas na aplicação, (Alterando o JSON, apenas para não salvar dados repetidos) que ficará dessa forma:
Figura 25 - Chamada no endpoint do método POST/Insert
Observe que para fazer a requisição, é solicitado um método POST, utilizando a o “endpoint” da aplicação conforme definimos na camada “controller”, passando um JSON representando um objeto “PeopleDTO”. Após essas três chamadas, pode-se verificar pelo DBeaver novamente, que os dados foram salvos e estão dessa forma:
Figura 26 - Dados salvos no banco de dados
Agora que o banco de dados já está “povoado”, podemos fazer os testes dos métodos findAll e findByID, nesse caso, os dois métodos, são métodos GET, e utilizaremos no POSTMAN para acessar esses métodos os “endpoints” http://localhost:8090/v1/people e http://localhost:8090/v1/people/1 lembrando que o numero no final dd “endpoint” do método findByID, é o ID que será buscado no banco de dados.
Com a aplicação ainda “rodando”, acessamos os “endpints” e obtemos os seguintes exemplos:
Figura 27 - Chamada no endpoint do método GET/findAll
Figura 28 - Chamada no endpoint do método GET/findByID
Observe que ao fazer essa requisição no “endpoint” findAll, é retornado uma lista de objetos People, como esperado, e no caso da requisição do “endpoint” findByID, retorna-se um objeto com ID que foi passado na própria “url”.
Para fazer o teste do método update, que pretendemos alterar algum campo do objeto no banco de dados, será um método do tipo PUT, que acessa o método Update, nessa requisição, é essencial, que seja passado um “body” contendo o ID e os demais campos do objeto onde será atualizado, dessa forma:
Figura 29 - Chamada no endpoint do método PUT/update
Ao realizar a chamada com o “body” conforme a figura acima, foi alterado no banco de dados o objeto com ID igual “2”, os campos conforme o JSON.
Para finalizar os devidos testes, façamos um ultimo teste, o método Delete, lembrando que é apenas para teste, pois esse método, deletará o objeto com ID de referência, e esse objeto não poderá ser recuperado após a exclusão.
Para esse método utilizaremos a “url” http://localhost:8090/v1/people/2 , portanto o objeto com ID “2”, será deletado. Um outro detalhe, é que como será deletado, a resposta da requição deverá ser vazia, contendo apenas o retorno do status “204” e “No Content” ficando da seguinte forma:
Figura 30 - Chamada no endpoint do método DELETE/delete
Este é o meu primeiro artigo e espero que tenha sido útil. Sou Wesley Silvestre Corrêa, e o que eu puder ajudar, estarei disponível, pois ainda estou em fase de aprendizado, busco e estudo muito para atingir meus objetivos, e sempre tive comigo, que quem ajuda o próximo, também está se ajudando, conhecimento se adquire compartilhando.
Escreverei outros artigos utilizando esse como base, melhorando o projeto e adicionando mais aprendizados e boas pratica, esse é apenas o inicio de um projeto que pretendo estender adicionando vários conhecimentos e exemplos.
Deixo aqui o repositório desse projeto para quais quer duvida e também os links para acessar o meu perfil no “github” e “linkedin”.
Repositório: https://github.com/WesleySCorrea/basic-crud-backend-java
Github: https://github.com