CRUD con Java Spring Boot y MySQL

Tutorial donde se explica paso a paso desde cero como hacer un CRUD básico con Java Spring Boot utilizando MySQL como base de datos, además aprovechamos JPA como API de persistencia.

Overview del proyecto

El proyecto tiene como fin realizar el CRUD para torres de apartamentos, en la cual almacenamos datos básicos como el nombre de la torre y la cantidad de apartamentos que dispone.

¿Qué es JPA?

JPA es una serie de implementaciones ORM (Object Relational Mapping) Mapeo Objeto Relacional que permite interactuar con la base de datos por medio de objetos, facilitando la conversión de objetos Java en instrucciones que el manejador de base de datos pueda entender. JPA nos ahorra tiempo a la hora de implementar un CRUD.

Creación del proyecto

Lo primero es buscar en el navegador https://start.spring.io/ donde creamos un proyecto de la siguiente manera:

inicio-proyecto-spring
  • Spring Web : Incluye las configuraciones necesarias para que nuestro proyecto tenga un Tomcat que actué como servidor.
  • MySQL Driver: Driver de conexión que nos permite entablar comunicación con la base de datos.
  • Spring Data JPA: Dependencia que incluye las configuraciones para hacer la implementación ORM explicado anteriormente.
  • Spring Boot DevTools: Nos provee un servidor que esta pendiente de cambios una vez este arriba el servidor, además de reinicios de servidor mas rápidos.

Configuración de la base de datos

Lo primero que debemos conocer es que spring.jpa nos permite crear las tablas de la base de datos a partir de las @Entity ( una entidad es la representación de una tabla de la base de datos). Por consiguiente lo único que se hace es crear la base de datos. Se puede utilizar Xampp, WampServer, MySQL Worbench o la consola de comandos directamente.

CREATE DATABASE IF NOT EXISTS CRUD CHARACTER SET utf8 COLLATE utf8_general_ci;

Configuración de build.gradle

Previamente cuando se creo el proyecto se utilizo gradle como gestor de dependencias, por lo tanto al abrir el proyecto en cualquier IDE tendremos el archivo build.gradle

estructura
Estructura inicial del proyecto

En el build.gradle ponemos las dependencias que hagan falta del siguiente listado

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.apache.commons:commons-lang3:3.6'

	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
	implementation 'org.springframework.boot:spring-boot-starter-validation:2.3.1.RELEASE'
	implementation 'org.hibernate.validator:hibernate-validator:6.1.2.Final'

}
  • Apache Commons Lang: Nos proporciona clases para manipular métodos String, métodos numéricos, reflexión de objetos, concurrencia, creación y serialización
  • Hibernate Validator: Validaciones que utilizamos para verificar condiciones en algún campo especifico que requerimos persistir, por ejemplo es muy común usar las etiquetas @NotBlank o @Min, con esta dependencia deberían aparecer estar etiquetas.

Conexión a base de datos y Configuración de application.properties

La conexión a MySQL la hacemos con spring.datasource.url donde utilizamos el driver de conexión como se muestra abajo.

Vamos a decirle a spring.jpa que cree las tablas a partir de las entidades que estén denotadas por @Entity con spring.jpa.hibernate.ddl-auto = update

spring.datasource.url= jdbc:mysql://localhost:3306/crud?useSLL=false&serverTimezone=UTC&useLegacyDateTimeCode=false

#username and password de la base de datos
spring.datasource.username = root
spring.datasource.password =

#Mostrar SQL queries que se realizan
spring.jpa.show-sql = true 

#Actualiza la base de datos y crea la entidad
spring.jpa.hibernate.ddl-auto = update

#generate optimization hibernate SQL
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

Antes de pasar al siguiente paso recomiendo correr el proyecto hasta el momento, para verificar que no tengamos ningún error de conexión a base de datos.

Estructura del proyecto

Estructura del proyecto
  • entity: Paquete donde se crea la clase que representan la tabla de la base de datos.
  • repository: Paquete en el cual se crea la clase que tiene como particularidad que extiende de JpaRepository el cual nos permite gestionar las operaciones CRUD fundamentales.
  • service: Paquete en donde se crea la clase que tiene como fin hacer la implementación de los métodos que se definan para la aplicación.
  • dto: Paquete donde se crea la clase que se limita a ser un objeto de transferencia entre el cliente y el servidor, recordemos el principio de responsabilidad única, donde la idea es que la entidad como únicamente el modelo de la tabla de la base de datos.
  • controller: Paquete donde se crea la clase que actúa como controlador Rest, es decir exponer las Apis que se definan.

Creación de clases

En cada clase se encuentran comentarios que indican las partes mas importantes en el código

Clic derecho en el paquete entitiy -> new -> Java Class -> Torre

package com.amoelcodigo.crud.entity;

import javax.persistence.*;

//Notación para indicar que es una entidad
@Entity
//Tabla que corresponde a esta entidad
@Table(name = "torre")
public class Torre {
    
    //Llave primaria de la tabla
    @Id
    //Se le indica que el campo ID es Autonumerico
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int idTorre;
    private String nombreTorre;
    private int cantidadAptos;

    public Torre() {
    }

    /*
    Constructor con parametros
     */
    public Torre(String nombreTorre, int cantidadAptos) {
        this.nombreTorre = nombreTorre;
        this.cantidadAptos = cantidadAptos;
    }

    public int getIdTorre() {
        return idTorre;
    }

    public void setIdTorre(int idTorre) {
        this.idTorre = idTorre;
    }

    public String getNombreTorre() {
        return nombreTorre;
    }

    public void setNombreTorre(String nombreTorre) {
        this.nombreTorre = nombreTorre;
    }

    public int getCantidadAptos() {
        return cantidadAptos;
    }

    public void setCantidadAptos(int cantidadAptos) {
        this.cantidadAptos = cantidadAptos;
    }
}

Clic derecho en el paquete repository-> new -> Interface -> TorreRepository

package com.amoelcodigo.crud.repository;

import com.amoelcodigo.crud.entity.Torre;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

//Notación para indicar que es un repositorio
@Repository
public interface TorreRepository  extends JpaRepository<Torre, Integer> {
        // Con @Repository le indico los metodos principales select, create, update, delete

    //Convención sobre convicción
    //CrudRepository permite realizar busquedas por campo según la entidad
    Optional<Torre> findByNombreTorre(String nombreTorre);

    boolean existsByNombreTorre(String nombreTorre);
    
}

Clic derecho en el paquete service-> new -> Java Class -> TorreService

package com.amoelcodigo.crud.service;

import com.amoelcodigo.crud.entity.Torre;
import com.amoelcodigo.crud.repository.TorreRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.swing.text.html.Option;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

//Notación para indicar que es un servicio
@Service
//Asegura que toda la data requerida este segura hasta que la transacción termine
//Recomiendo leer acerca de esta notación (es un mundo completo jeje) 
@Transactional
public class TorreService {

    //Inyección de dependecias (crea una instancia cuando lo requiera)
    @Autowired
    TorreRepository torreRepository;

    //Por defecto el repositorio al extender de JPA trae el metodo por defecto
    public List<Torre> listaTorre(){
        return  torreRepository.findAll();
    }

    public Optional<Torre> getTorre(int idTorre){
        return  torreRepository.findById(idTorre);
    }

    public Optional<Torre> getByNombreTorre(String nombreTorre){
        return torreRepository.findByNombreTorre(nombreTorre);
    }

    public void saveTorre(Torre torre){
        torreRepository.save(torre);
    }

    public void deleteTorre(int idTorre){
        torreRepository.deleteById(idTorre);
    }

    public boolean existsByIdTorre(int idTorre){
        return torreRepository.existsById(idTorre);
    }

    public boolean existsByNombreTorre(String nombreTorre){
        return torreRepository.existsByNombreTorre(nombreTorre);
    }

}

Clic derecho en el paquete dto-> new -> Java Class -> Mensaje

Clic derecho en el paquete dto-> new -> Java Class -> TorreDTO

package com.amoelcodigo.crud.dto;

//Clase para mostrar mensajes por pantalla en el cliente (front)
public class Mensaje {

    private String mensaje;

    public Mensaje(String mensaje) {
        this.mensaje = mensaje;
    }

    public String getMensaje() {
        return mensaje;
    }

    public void setMensaje(String mensaje) {
        this.mensaje = mensaje;
    }
}
package com.amoelcodigo.crud.dto;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

public class TorreDto {

    //Notación para especificar que el campo no puede venir vacio
    @NotBlank
    private String nombreTorre;
    //Notación para indicar que el tamaño minimo debe ser 0
    @Min(0)
    private int cantidadAptos;

    public TorreDto() {
    }

    public TorreDto(String nombreTorre, int cantidadAptos) {
        this.nombreTorre = nombreTorre;
        this.cantidadAptos = cantidadAptos;
    }

    public String getNombreTorre() {
        return nombreTorre;
    }

    public void setNombreTorre(String nombreTorre) {
        this.nombreTorre = nombreTorre;
    }

    public int getCantidadAptos() {
        return cantidadAptos;
    }

    public void setCantidadAptos(int cantidadAptos) {
        this.cantidadAptos = cantidadAptos;
    }
}

Clic derecho en el paquete controller-> new -> Java Class -> TorreController

package com.amoelcodigo.crud.controller;

import com.amoelcodigo.crud.dto.Mensaje;
import com.amoelcodigo.crud.dto.TorreDto;
import com.amoelcodigo.crud.entity.Torre;
import com.amoelcodigo.crud.service.TorreService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//Notación para indicar que es un controlador de tipo Rest
@RestController
//Notación para indicar el contexto de nuestros endpoint es decir /torre/nombreServicio
@RequestMapping("/torre")
//URL que permitimos que consuman nuestras APIS
//En caso de querer permitir todos los origentes ponemos en lugar de la URL un *
@CrossOrigin(origins = "http://localhost:4200")
public class TorreController {
  
   /*El nombre de las torres es unico,
    en la creación y actualizacón se hace la validación*/
  
    //Inyección de dependencias
    @Autowired
    TorreService torreService;

    //Se le indica el tipo de petición asi como el nombre del servicio
    @GetMapping("/listaTorre")
    public ResponseEntity<List<Torre>> listaTorres(){

        List<Torre> torres = torreService.listaTorre();
        return new ResponseEntity<List<Torre>>(torres, HttpStatus.OK);
    }

    @GetMapping("/detalleTorre/{idTorre}")
    public ResponseEntity<Torre> torreById(@PathVariable("idTorre") int idTorre){

        if (!torreService.existsByIdTorre(idTorre))
            return new ResponseEntity(new Mensaje("No existe la torre"), HttpStatus.NOT_FOUND);

        Torre torre = torreService.getTorre(idTorre).get();
        return new ResponseEntity(torre, HttpStatus.OK);
    }

    @GetMapping("/detalleNombre/{nombreTorre}")
    public ResponseEntity<Torre> torreByNombre(@PathVariable("nombreTorre") String nombreTorre){

        if (!torreService.existsByNombreTorre(nombreTorre))
            return new ResponseEntity(new Mensaje("No existe la torre"), HttpStatus.NOT_FOUND);

        Torre torre = torreService.getByNombreTorre(nombreTorre).get();
        return new ResponseEntity(torre, HttpStatus.OK);
    }

    //Con el ? le decimos que no devulve ningún tipo de dato
    //El body va a ser un JSON
    //Aqui se usa el apache commons lang
    // El import de StringUtils es import org.apache.commons.lang3.StringUtils;
    @PostMapping("/crearTorre")
    public ResponseEntity<?> creaTorre(@RequestBody TorreDto torreDto){

        if(StringUtils.isBlank(torreDto.getNombreTorre()))
            return new ResponseEntity(new Mensaje("El nombre es obligatorio"), HttpStatus.BAD_REQUEST);

        if(torreDto.getCantidadAptos()<0 || (Integer) torreDto.getCantidadAptos() == null)
            return new ResponseEntity(new Mensaje("La cantidad de aptos debe ser mayor a 0"), HttpStatus.BAD_REQUEST);

        if(torreService.existsByNombreTorre(torreDto.getNombreTorre()))
            return new ResponseEntity(new Mensaje("Ya existe una torre con ese nombre"), HttpStatus.BAD_REQUEST);

        Torre torre = new Torre(torreDto.getNombreTorre(), torreDto.getCantidadAptos());
        torreService.saveTorre(torre);
        return new ResponseEntity(new Mensaje("Torre creada"), HttpStatus.OK);
    }

    @PutMapping("/actualizarTorre/{idTorre}")
    public ResponseEntity<?> actualizarTorre(@PathVariable("idTorre") int idTorre, @RequestBody TorreDto torreDto){

        if (!torreService.existsByIdTorre(idTorre))
        return new ResponseEntity(new Mensaje("No existe la torre"), HttpStatus.NOT_FOUND);

        if (torreService.existsByNombreTorre(torreDto.getNombreTorre())
                && torreService.getByNombreTorre(torreDto.getNombreTorre()).get().getIdTorre() != idTorre)
            return new ResponseEntity(new Mensaje("El nombre de la torre ya existe"), HttpStatus.NOT_FOUND);

        if(StringUtils.isBlank(torreDto.getNombreTorre()))
            return new ResponseEntity(new Mensaje("El nombre es obligatorio"), HttpStatus.BAD_REQUEST);

        if(torreDto.getCantidadAptos()<0 || (Integer) torreDto.getCantidadAptos() == null)
            return new ResponseEntity(new Mensaje("La cantidad de aptos debe ser mayor a 0"), HttpStatus.BAD_REQUEST);

        Torre torre = torreService.getTorre(idTorre).get();
        torre.setNombreTorre(torreDto.getNombreTorre());
        torre.setCantidadAptos(torreDto.getCantidadAptos());
        torreService.saveTorre(torre);
        return new ResponseEntity(new Mensaje("Torre actualizada"), HttpStatus.OK);
    }

    @DeleteMapping("/borrarTorre/{idTorre}")
    public ResponseEntity<?> borrarTorre(@PathVariable("idTorre") int idTorre){
        if (!torreService.existsByIdTorre(idTorre))
            return new ResponseEntity(new Mensaje("No existe la torre"), HttpStatus.NOT_FOUND);
        torreService.deleteTorre(idTorre);
        return new ResponseEntity(new Mensaje("Torre eliminada"), HttpStatus.OK);
    }

}

Run del proyecto

Corremos el proyecto con la tarea de bootRun que traen las IDEs en caso dado que no encuentren la tarea pueden correr el proyecto desde consola con:

gradle bootRun

Al momento de correr nuestra aplicación nuestra tabla se creara automáticamente

Creación de la tabla gracias a la implementación de JPA
tabla-mysql
Muestra de creación de la tabla desde phpMyAdmin

Si todo funciona correctamente nos indica Started CrudApplication y el puerto por el que esta corriendo.

Probando los servicios REST

Utilizamos Postman para probar los servicios

Prueba del modo para crear una torre
Servicio para listar todas las torres

Colección de Postman para pruebas

En el siguiente link esta la colección de Postman

Repositorio del proyecto

En el siguiente link esta el repositorio del proyecto

Conclusión

Aprovechando Spring Boot y JPA se pueden hacer CRUD de manera rápida y muy eficaces, además utilizando la arquitectura propuesta en este proyecto se respeta el principio de responsabilidad única gracias al DTO.

Cualquier duda o comentario no dudes en deja un comentario, o contacta conmigo:

One Reply to “CRUD con Java Spring Boot y MySQL”

Leave a Reply

Your email address will not be published.