Neste artigo, vamos complementar o vídeo gravado mostrando, na prática, como construir os métodos para cadastrar e atualizar uma categoria em um sistema backend utilizando Spring Boot.
Este conteúdo é especialmente pensado para estudantes que estão vendo Spring Boot pela primeira vez, explicando cada detalhe de forma simples e objetiva.
1. Salvando uma nova Categoria (POST)
CategoriaController.java
@PostMapping
public ResponseEntity<CategoriaDTO> salvarCategoria(@RequestBody CategoriaDTO categoriaDTO) {
categoriaDTO = categoriaService.salvarCategoria(categoriaDTO);
URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri()
.path("/{id}").buildAndExpand(categoriaDTO.getId()).toUri();
return ResponseEntity.created(uri).body(categoriaDTO);
}
CategoriaService.java
@Transactional
public CategoriaDTO salvarCategoria(CategoriaDTO categoriaDTO) {
Categoria categoria = new Categoria();
categoria.setNome(categoriaDTO.getNome());
categoria = categoriaRepository.save(categoria);
return new CategoriaDTO(categoria);
}
Explicação do fluxo:
Passo a passo:
- Quando um cliente (como o Postman ou uma aplicação frontend) envia uma requisição POST com dados da categoria, o método
salvarCategoria()
do controller é chamado. - O Spring Boot automaticamente converte o JSON recebido em um objeto
CategoriaDTO
. - Esse DTO é enviado para a camada de serviço (
CategoriaService
). - Na service:
- Criamos uma nova instância da classe
Categoria
(não é DTO, é a entidade que representa a tabela no banco). - Copiamos os dados do DTO para a entidade.
- Usamos o
categoriaRepository.save(categoria)
para salvar a nova categoria no banco.
- Criamos uma nova instância da classe
- Depois de salvo, voltamos para o controller, construímos uma URI que aponta para o novo recurso criado, e retornamos a resposta HTTP 201 Created.
Por que usamos save()
aqui?
- Porque estamos criando um novo objeto que ainda não existe no banco.
- O
save()
instrui o Hibernate a gerar o comandoINSERT INTO categoria (...) VALUES (...)
para armazenar os dados.
Resumo:
Se criamos um novo objeto, precisamos chamar
save()
para gravar no banco.
2. Atualizando uma Categoria Existente (PUT)
CategoriaController.java
@PutMapping(value = "/{id}")
public ResponseEntity<CategoriaDTO> atualizarCategoria(@PathVariable Long id, @RequestBody CategoriaDTO categoriaDTO) {
categoriaDTO = categoriaService.atualizarCategoria(id, categoriaDTO);
return ResponseEntity.ok().body(categoriaDTO);
}
CategoriaService.java
@Transactional
public CategoriaDTO atualizarCategoria(Long id, CategoriaDTO categoriaDTO) {
Categoria categoria = categoriaRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Id não encontrado: " + id));
categoria.setNome(categoriaDTO.getNome());
return new CategoriaDTO(categoria);
}
Explicação do fluxo:
Passo a passo:
- Quando um cliente faz uma requisição PUT para atualizar uma categoria existente, o Spring Boot captura o
id
da URL e o corpo do JSON comoCategoriaDTO
. - A service tenta buscar a categoria no banco usando
findById(id)
. - Se a categoria não for encontrada, lançamos uma exceção
EntityNotFoundException
, que depois será tratada para devolver um erro 404. - Se a categoria for encontrada:
- Atualizamos o campo
nome
com o novo valor recebido. - Não chamamos
save()
! Por quê?- Como estamos dentro de uma transação (
@Transactional
), o Hibernate detecta automaticamente a alteração (chamamos isso de dirty checking) e gera o comandoUPDATE
no banco ao final da transação.
- Como estamos dentro de uma transação (
- Atualizamos o campo
- Por fim, retornamos um
CategoriaDTO
atualizado para o cliente.
Por que aqui não usamos save()
?
- Quando carregamos um objeto com
findById()
dentro de uma transação ativa, ele passa a ser gerenciado pelo Hibernate. - O Hibernate monitora as mudanças e sincroniza automaticamente com o banco ao dar commit na transação.
Resumo:
Se buscamos o objeto e estamos dentro de
@Transactional
, o Hibernate atualiza o banco automaticamente.
3. Entendendo a expressão lambda () ->
Dentro do método de atualização, temos este trecho:
categoriaRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Id não encontrado: " + id));
Aqui usamos uma expressão lambda:
()
indica que não estamos recebendo nenhum parâmetro.->
indica que o que vem depois é o que será executado.new EntityNotFoundException(...)
cria e retorna uma nova exceção.
Essa expressão é utilizada porque o método orElseThrow
espera uma função que forneça uma exceção. Em outras palavras, estamos dizendo:
“Se o objeto não for encontrado, execute essa função que cria uma nova exceção.”
Por que usar lambda?
- Deixa o código mais limpo e mais legível.
- Evita ter que criar uma classe anônima ou um método separado só para isso.
Exemplo sem lambda (forma antiga):
categoriaRepository.findById(id)
.orElseThrow(new Supplier<EntityNotFoundException>() {
@Override
public EntityNotFoundException get() {
return new EntityNotFoundException("Id não encontrado: " + id);
}
});
Com a expressão lambda () ->
, o código fica muito mais enxuto e fácil de ler.
Conclusão
- Usamos
save()
para criar um novo objeto no banco de dados. - Atualizamos diretamente os objetos carregados dentro de uma transação sem necessidade de
save()
extra. - Sempre trabalhamos com
DTOs
para proteger as entidades e controlar melhor os dados que entram e saem da nossa API. - A separação entre Controller → Service → Repository é essencial para manter o código limpo e organizado.
- A utilização de expressões lambda deixa o código mais moderno, claro e objetivo.