Como construir uma API de gestão financeira com Java, Spring Boot, JPA, Hibernate e H2 - Parte II

Como construir uma API de gestão financeira com Java, Spring Boot, JPA, Hibernate e H2 - Parte II

Introdução

Este artigo é uma continuação do artigo Como construir uma API de gestão financeira utilizando Java, Spring Boot, JPA, Hibernate e H2 - Parte I. Portanto, como pré-requisito e introdução propriamente aprofundada, peço que o leia.

Nesse artigo, como parte II, será apresentada a continuidade do projeto da API de gestão financeira através da criação dos outros elementos que o envolvem.

Projeto

Com base no último commit feito no artigo anterior, serão criadas todas as camadas para os elementos a seguir:

  • Receita
  • ReceitaStatus
  • Despesa
  • DespesaStatus
  • Categoria

O projeto se encontra no seguinte repositório no Github: https://github.com/caiocv18/artigojava

Receita

A entidade Receita do sistema será utilizada para que o Usuario, criado no artigo anterior, consiga registrar todo dinheiro que entra no controle financeiro.

Imagem I - Entidade Receita

Para a classe Receita, teremos apenas os atributos a seguir:

  • id;
  • titulo;
  • valor;
  • data.

Com os métodos get e set para cada atributo.

Devido às anotações e funções que serão utilizadas, também se faz necessária a criação dos métodos equals e hashCode.

Criando a entidade Receita

  1. Crie a classe Receita dentro do pacote entidades;
  2. Adicione os atributos como private:
  • Long id;
  • String titulo;
  • Double valor;
  • Instant data;

3. Crie um construtor vazio;

4. Crie um construtor com todos os atributos;

5. Gere os getters e setters;

6. Gere hashcode e equals (selecionar apenas o atributo de ID para fazer a comparação);

7. Implemente a interface Serializable;

8. Adicione o número de série padrão sugerido pelo próprio IntelliJ para o Serializable;

9. Adicione @Entity acima da declaração da classe;

10. Adicione @Table(name = "tb_receita") logo abaixo de @Entity;

11. Adicione @Id logo acima da declaração do atributo id;

12. Adicione @GeneratedValue(strategy = GenerationType.IDENTITY) logo abaixo de @Id.

Despesa

A entidade Despesa do sistema será utilizada para que o Usuario, criado no artigo anterior, consiga registrar todo dinheiro que sai no controle financeiro.

Imagem II - Entidade Despesa

Para a classe Despesa, teremos apenas os atributos a seguir:

  • id;
  • titulo;
  • valor;
  • data.

Com os métodos get e set para cada atributo.

Devido às anotações e funções que serão utilizadas, também se faz necessária a criação dos métodos equals e hashCode.

Criando a entidade Despesa

  1. Crie a classe Despesa dentro do pacote entidades;
  2. Adicione os atributos como private:
  • Long id;
  • String titulo;
  • Double valor;
  • Instant data;

3. Crie um construtor vazio;

4. Crie um construtor com todos os atributos;

5. Gere os getters e setters;

6. Gere hashcode e equals (selecione apenas o atributo de ID para fazer a comparação);

7. Implementer a interface Serializable;

8. Adicione o número de série padrão sugerido pelo próprio IntelliJ para o Serializable;

9. Adicione @Entity acima da declaração da classe;

10. Adicione @Table(name = "tb_despesa") logo abaixo de @Entity;

11. Adicione @Id logo acima da declaração do atributo id;

12. Adicione @GeneratedValue(strategy = GenerationType.IDENTITY) logo abaixo de @Id.

Relações entre entidades

Um ponto muito importante na arquitetura do projeto, é como os objetos se relacionam entre si. Neste projeto, cada usuário pode adicionar qualquer número que ele desejar de receitas e de despesas, portanto, isso nos leva a uma relação 1 → N, sendo um usuário adicionando N receitas ou despesas.

N simboliza que o usuário pode adicionar uma, nenhuma ou várias receitas/despesas.

Conforme previsto no diagrama:

Imagem III - Relações entre as entidades Usuário, Receita e Despesa

Adicionando relação entre Receita e o Usuário

  1. Acesse a classe Receita;
  2. Adicione o atributo Usuario:
private Usuario usuario;

3. Adicione a anotação @ManyToOne;

ℹ️ Anotação JPA para informar que a classe em questão pode possuir muitos registros relacionados a um único registro do atributo especificado.

4. Adicione a anotação @JoinColumn(name = "usuario_id")

ℹ️ Informa qual coluna deve ser utilizada para fazer a junção que associa ambos os elementos envolvidos.

5. Gere o getter e setter para o novo atributo Usuario.

Adicionando relação entre Despesa e o Usuário

  1. Acesse a classe Despesa;

2. Adicione o atributo Usuario:


private Usuario usuario;

3. Adicione a anotação @ManyToOne;

ℹ️ Anotação JPA para informar que a classe em questão pode possuir muitos registros relacionados a um único registro do atributo especificado.

4. Adicione a anotação @JoinColumn(name = "usuario_id")

ℹ️ Informa qual coluna deve ser utilizada para fazer a junção que associa ambos os elementos envolvidos.

5. Gere o getter e setter para o novo atributo Usuario.

Adicionando relação entre Usuário e Despesa/Receita

  1. Acesse a classe Usuario;
  2. Adicione o atributo de lista de Receita:
private List<Receita> receitas = new ArrayList<>();

3. Adicione a anotação @OneToMany(mappedBy = "usuario") acima do atributo que foi criado no passo anterior;

ℹ️ Anotação JPA para informar que um registro da classe em questão pode possuir vários registros do atributo especificado.

4. Adicione o atributo de lista de Despesa:

private List<Despesa> receitas = new ArrayList<>();

5. Adicione a anotação @OneToMany(mappedBy = "usuario") acima do atributo que foi criado no passo anterior;

ℹ️ A anotação é exatamente igual devido ao fato de serem duas entidades diferentes relacionada à mesma entidade Usuario

6. Gere os getters e setters para os dois novos atributos de listas.

Sendo assim, ao executar novamente a aplicação e entrar no console do banco de dados H2, será possível visualizar as duas novas tabelas criadas para Receita e Despesa.

GIF I - Visualizando as tabelas Receita e Despesa no console do H2

ℹ️ Link do meu commit relacionado aos passos realizados acima:

Receita e Despesa nas outras camadas de aplicação

Muitos dos conceitos envolvendo as camadas de serviço, repositório e recurso já foram explicadas e colocadas em prática na primeira parte deste artigo, portanto, seguiremos o mesmo modelo, copiando as classes criadas para a entidade Usuário.

Repositório

  1. Com base na classe UsuarioRepositorio, criar a classe ReceitaRepositorio;
  2. Com base na classe UsuarioRepositorio, criar a classe DespesaRepositorio;

Serviço

  1. Com base na classe UsuarioService, criar a classe ReceitaService;
  2. Com base na classe UsuarioService, criar a classe DepesaService;

Recurso

  1. Com base na classe UsuarioRecurso, criar a classe ReceitaRecurso;
  2. Com base na classe UsuarioRecurso, criar a classe DespesaRecurso;

ℹ️ De fato não há diferença entre o que foi implementado para a entidade Usuario e as entidades Receita e Despesa, basta apenas alterar em cada classe tudo que diz respeito a Usuario.

Dados de teste

Para que seja possível visualizar de forma “mockada” algumas receitas e despesas, é necessário instanciar alguns dados no arquivo de configuração de teste:

public class TesteConfiguracao implements CommandLineRunner {

@Autowired

private UsuarioRepositorio usuarioRepositorio;

@Autowired

private ReceitaRepositorio receitaRepositorio;

@Autowired

private DespesaRepositorio despesaRepositorio;

@Override

public void run(String... args) throws Exception {

Usuario usuario1 = new Usuario("Caio", "caio@gmail.com", "123456");

Usuario usuario2 = new Usuario("Vinicius", "vinicius@gmail.com" ,"123456");

Receita receita1 = new Receita("Salário", 2900.00, Instant.now(), usuario1);

Receita receita2 = new Receita("Salário", 4000.00, Instant.now(), usuario2);

Receita receita3 = new Receita("Bônus", 500.00, Instant.now(), usuario1);

Despesa despesa1 = new Despesa("Gasolina", 50.00, Instant.now(), usuario1);

Despesa despesa2 = new Despesa("Conta de luz", 150.00, Instant.now(), usuario2);

Despesa despesa3 = new Despesa("Almoço", 25.00, Instant.now(), usuario1);

usuarioRepositorio.saveAll(Arrays.asList(usuario1,usuario2));

receitaRepositorio.saveAll(Arrays.asList(receita1,receita2,receita3));

despesaRepositorio.saveAll(Arrays.asList(despesa1,despesa2,despesa3));

}

}

Últimas correções

Visando uma melhor visualização da data no formato JSON quando for feita a requisição no /receitas ou no /despesas, é necessário adicionar uma anotação responsável por formatar a informação da data, ficando da seguinte forma:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "GMT-3")

private Instant data;

Ao utilizar a anotação de @ManyToOne e relacionar Receita/Despesa com Usuario, criamos uma relação dupla, onde Receita/Despesa aponta para o Usuario e Usuario também aponta para Receita/Despesa. Essa relação de ida e volta causa uma visualização confusa ao formar o JSON, pois faz com que entre em loop. Usuario “chama” os dados de Receita/Despesa que também “chama” o os dados do Usuario.

Para resolver este problema, precisamos escolher um dos lados para ignorar o “chamamento”. Neste caso, indico fazer com que Receita/Despesa ignore a etapa de “chamar” novamente a entidade Usuario utilizando @JsonIgnore da seguinte forma:

@ManyToOne

@JoinColumn(name = "usuario_id")

@JsonIgnore

private Usuario usuario;

Sendo assim, ao executar novamente a aplicação, será possível fazer uma requisição para o /receitas e para o /despesas, de forma que o JSON de retorno já está sendo feito da melhor forma:

GIF II - Visualizando as requisições de /receitas e /despesas

ℹ️ Link do meu commit relacionado aos passos realizados acima: Criando Serviços, Repositórios e Recursos para as entidades …7eed1c4

Criação de Status

Para cada tipo de cadastro no sistema, sendo uma Receita ou Despesa, também será possível escolher um status para cada um. Portanto, é necessário criar essas novas entidades conforme apresentado anteriormente no fluxo e fazer as relações entre suas respectivas entidades.

Imagem IV - Relação entre Receita e ReceitaStatus e Imagem V - Relação entre Despesa e DespesaStatus

Conforme ilustrado, a relação entre as entidades é de n para 1, sendo: para uma quantidade n de Receitas/Despesas, cada uma só poderá ter 1 status. O contrário sendo que 1 status pode estar em n Receitas/Despesas.

Criando ReceitaStatus e DespesaStatus

  1. Crie um pacote para enums;
  2. Crie um Enum dentro do pacote com o nome ReceitaStatus;

  1. Utilize os seguintes status:

  1. RECEBIDA
  2. PENDENTE
  3. AGENDADA
  4. ATRASADA

2. Crie um atributo chamado código;

3. Crie um construtor que utiliza o código como parâmetro na construção;

3. Crie um get para o código

4. Crie uma função estática chamada valorDoCodigo que retorna o Enum do código passado como parâmetro;

public static ReceitaStatus valorDoCodigo(int codigo){

for (ReceitaStatus valor : ReceitaStatus.values()){

if (value.getCodigo() == codigo){

return valor

}

}

throw new IllegalArgumentException("Código inválido para o status de uma Receita")

}

5. Crie um Enum dentro do pacote com o nome DespesaStatus

  1. Utilize os seguintes status:

  1. PAGA
  2. PENDENTE
  3. AGENDADA
  4. ATRASADA

2. Crie um atributo chamado código;

3. Crie um construtor que utiliza o código como parâmetro na construção;

6.  Adicione o atributo utilizando o Enum em cada classe Receita e Despesa;

7. Gere os getters e setters;

8. Utilize a lógica de valorDoCodigo dentro de cada get e set;

9. Alterar as declarações das instâncias que são utilizadas no TesteConfiguracao.

ℹ️ Link do meu commit relacionado aos passos realizados acima: Adicionando status para Receitas e Despesasa2f16b8

Criação da Categoria e relação com Receita / Despesa

A entidade Categoria serve para que possamos classificar despesas e receitas em grupos de assunto como “Combustível”, “Alimentação”, “Trabalho” e “Contas mensais”.

Conforme previsto no diagrama, a entidade Categoria se relaciona com Receita e Despesa onde a relação é de 1 para N (uma categoria pode ser utilizada em N Receita / Despesa). Temos apenas uma entidade Categoria para ser utilizado em ambos os tipos de transação, sendo diferente do que tínhamos anteriormente para os Status.

Imagem VI - Relação entre Categoria e Receita e Despesa

Criando a entidade Categoria

  1. Crie a entidade Categoria dentro do pacote de entidades
  2. Coloque os seguinte atributos e respectivos tipos de dados:

Long id;

String titulo;

3. Crie um construtor vazio

4. Crie um construtor com todos os atributos

5. Gere os Getters e setters

6. Gere as funções Hashcode e equals utilizando o atributo id

7. Adicione a interface Serializable

8. Gere um SerialId

9. Adicione a anotação @Entity

10. Adicione a anotação@Table(name = "tb_categoria")

Repositório

Com base na classe UsuarioRepositorio, criar a classe CategoriaRepositorio;

Serviço

Com base na classe UsuarioService, criar a classe CategoriaService;

Recurso

Com base na classe UsuarioRecurso, criar a classe CategoriaRecurso;

Adicionando a relação com Receita e Despesa

  1. Dentro da Classe categoria:

  1. Crie um atributo do tipo List para Receita;
  2. Crie um atributo do tipo List para Despesa;
  3. Adicione em ambos a anotação @OneToMany;
  4. Adicione em ambos a anotação @JsonIgnore;
  5. Gere método Get para ambos

2. Ficando com a classe da seguinte forma:

@OneToMany(mappedBy = "categoriaReceita")

@JsonIgnore

private List<Receita> receitas = new ArrayList<>();

@OneToMany(mappedBy = "categoriaDespesa")

@JsonIgnore

private List<Despesa> despesas = new ArrayList<>();

...

public List<Receita> getReceitas() {

return receitas;

}

public List<Despesa> getDespesas() {

return despesas;

}

...

2. Dentro da classe Receita:

  1. Crie o atributo categoriaReceita do tipo Categoria;
  2. Adicione a anotação @ManyToOne;
  3. Adicione a anotação @JoinColumn(name = "categoria_receita_id");
  4. Adicione o novo atributo categoriaReceita ao construtor;
  5. Gere os métodos Get e Set para o atributo categoriaReceita.

3. Ficando com a classe da seguinte forma:

@ManyToOne

@JoinColumn(name = "categoria_receita_id")

private Categoria categoriaReceita;

...

public Receita(String titulo, Double valor, Instant data, ReceitaStatus status, Usuario usuario, Categoria categoriaReceita) {

super();

this.titulo = titulo;

this.valor = valor;

this.data = data;

setStatus(status);

this.usuario = usuario;

this.categoriaReceita = categoriaReceita;

}

...

public Categoria getCategoriaReceita() {

return categoriaReceita;

}

public void setCategoriaReceita(Categoria categoriaReceita) {

this.categoriaReceita = categoriaReceita;

}

Dados de teste

Para que seja possível visualizar de forma “mockada” algumas categorias, assim como foi feito para as outras entidades, é necessário instanciar alguns dados no arquivo de configuração de teste:

public class TesteConfiguracao implements CommandLineRunner {

@Autowired

private UsuarioRepositorio usuarioRepositorio;

@Autowired

private ReceitaRepositorio receitaRepositorio;

@Autowired

private DespesaRepositorio despesaRepositorio;

@Override

public void run(String... args) throws Exception {

Usuario usuario1 = new Usuario("Caio", "caio@gmail.com", "123456");

Usuario usuario2 = new Usuario("Vinicius", "vinicius@gmail.com" ,"123456");

Categoria categoria1 = new Categoria("Combustível");

Categoria categoria2 = new Categoria("Alimentação");

Categoria categoria3 = new Categoria("Trabalho");

Categoria categoria4 = new Categoria("Contas mensais");

categoriaRepositorio.saveAll(Arrays.asList(categoria1, categoria2, categoria3, categoria4));

Receita receita1 = new Receita("Salário", 2900.00, Instant.now(), usuario1, categoria3);

Receita receita2 = new Receita("Salário", 4000.00, Instant.now(), usuario2, categoria3);

Receita receita3 = new Receita("Bônus", 500.00, Instant.now(), usuario1, categoria3);

Despesa despesa1 = new Despesa("Gasolina", 50.00, Instant.now(), usuario1, categoria1);

Despesa despesa2 = new Despesa("Conta de luz", 150.00, Instant.now(), usuario2, categoria4);

Despesa despesa3 = new Despesa("Almoço", 25.00, Instant.now(), usuario1, categoria2);

usuarioRepositorio.saveAll(Arrays.asList(usuario1,usuario2));

receitaRepositorio.saveAll(Arrays.asList(receita1,receita2,receita3));

despesaRepositorio.saveAll(Arrays.asList(despesa1,despesa2,despesa3));

}

}

Sendo assim, ao executar novamente a aplicação, será possível fazer uma requisição para o /categorias, podendo ver a relação com cada Receita e Despesa também pelas requisições no /receitas e /despesas:

ℹ️ Link dos meus commits relacionados aos passos realizados acima: 1 - Adicionando a entidade Categoriacf3ef48 2 - Adicionando a relação entre Despesa e Categoriaeac0a46 3 - Adicionando a relação entre Receita e Categoriac652d75

Conclusão

Lembrando, este artigo é uma continuação da parte I, que é fundamental para o entendimento total, caso não tenha visualizado anteriormente, segue o link:

Como construir uma API de gestão financeira utilizando Java, Spring Boot, JPA, Hibernate e H2 - Parte I

Depois de mais alguns parágrafos, commits, conceitos, da implementação de novas entidades em todas as camadas do sistema e de cerca de mais 30 minutos de leitura estimados, é um bom momento para digerir com calma todas as informações que foram apresentadas e checar se realmente foi possível consolidar todos os ensinamentos, além de colocá-los em prática em algum outro projeto.

Com toda certeza, o sistema ainda não está pronto, apesar de já estar todo desenhado de acordo com o diagrama, tendo todas as respectivas relações entre entidades.

O artigo, com a implementação das demais funcionalidades e os avanços em outros conceitos continua na parte III:

Como construir uma API de gestão financeira utilizando Java, Spring Boot, JPA, Hibernate e H2 - Parte III

Após a apresentação de tantos conceitos também colocados em prática neste artigo, a parte III será a última e estará voltada para a implementação do restante das letras do CRUD, para que seja possível adicionar, atualizar e deletar cada um dos elementos do sistema e de colocar a aplicação em um ambiente hospedado na nuvem utilizando o Heroku.

Agradeço por chegar até aqui e te desejo sucesso nessa nova aventura utilizando Java, Spring, JPA, Hibernate e H2!

Te espero na parte III para continuarmos a implementação do sistema da API de gestão financeira.

Segue meu contato e meu site em caso de alguma dúvida, necessidade ou sugestão:

Referências

  1. Curso do Nélio Alves com diversos projetos utilizando Java disponível na Udemy
    Java COMPLETO 2023 Programação Orientada a Objetos +Projetos
  2. JPA - Muitos-para-Um (ManyToOne)
    JPA - Muitos-para-Um (ManyToOne)
  3. @JoinColumn Annotation Explained
    @JoinColumn Annotation Explained | Baeldung

4. Hibernate One to Many Annotation Tutorial
Hibernate One to Many Annotation Tutorial | Baeldung

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.