Curso completo de SPRING BOOT CRUD + JPA + POSTGRESQL +REST + Angular

Angular y Spring Boot es una poderosa combinación, muchas empresas están haciendo sus frontend en Angular y el backend en Spring Boot con Java, es por eso que en este curso gratis aprenderás como crear un CRUD usando Angular y Spring Boot.

Tabla de contenido
  1. ¿Qué significa CRUD en desarrollo de software?
  2. Herramientas necesarias
  3. Stack tecnológico
  4. Documentación oficial
  5. Creando un proyecto en Spring Boot
  6. Creando modelo de datos
    1. Clase Estado
    2. Clase Pais
    3. Clase Persona
  7. Creando repositorios y servicios con Spring Boot
    1. Repositorios
    2. Servicios
  8. Creando un API REST con Spring Boot
  9. Crear un proyecto en Angular
  10. Conectar backend Spring Boot y frontend Angular
  11. Haciendo un refactor al código
    1. ¿Qué es hacer refactor?
    2. Refactor del backend
    3. Cambios en el modelo de datos
    4. Cambios en los servicios
    5. Cambios en el POM
    6. Refactor del frontend
  12. Código fuente refactor
  13. Código fuente
    1. Frontend
    2. Backend
    3. ¿Cómo descargar el código?

¿Qué significa CRUD en desarrollo de software?

Recordemos que un CRUD trae las operaciones de Crear, Leer, Actualizar y Borrar. Estas siglas vienen del ingles Create, Read, Update y Delete.

Un CRUD es el proyecto mas cliché de todos los tiempos, pero nos sirve para tener una noción clara sobre las principales herramientas que nos brindan las tecnologías que vamos a estudiar.

Nota: si vienes de mi canal de Youtube Yo Androide y solo te interesa descargar el código, ve hasta la parte final de este post y dirígete hasta la ultima pagina, en donde están los enlaces para descargar el código del frontend y el backend.

Herramientas necesarias

Para este curso gratuito sobre la creacion de un CRUD con Angular y Spring Boot, usaremos las siguientes herramientas (si no las tienes instaladas, deberás hacerlo):

  • Visual Studio Code para programar el front
  • IntelliJ IDEA, Eclipse o Spring Tool Suite (STS) para programar el backend
  • Node
  • Java 1.8 o superior
  • PGAdmin

Stack tecnológico

  • Spring Boot
  • Angular
  • PostgreSQL

Documentación oficial

Como base de datos, usaremos una base de datos relacional PostgreSQL en donde guardaremos algunas tablas relacionadas entre si, ya sea uno a muchos, como muchos uno.

Nota: si no entiendes mucho de PostgreSQL, te recomiendo primero ver este video que hice explicando los conceptos basicos de PostgreSQL.

En realidad, todo el contenido va a estar explicado de forma detallada en mi canal de YouTube siguiendo la lista de reproducción que estará al final de este post.

Creando un proyecto en Spring Boot

Para crear el proyecto en Spring Boot he usado STS (Spring Tool Suite), pero también pueden usar el inicializador de Spring Boot.

Una vez creado el backend, tendremos el archivo POM así (en caso de que hayan decidido usan maven)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>xyz.yoandroide.persona</groupId>
	<artifactId>PersonaBACKEND</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>PersonaBACKEND</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>11</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Creando modelo de datos

El modelo de datos con el que vamos a trabajar es bastante sencillo. Tendremos una persona que vive en un pais y un estado. Es decir, que la persona se relaciona con un único estado, así mismo, el estado estará asociado a un único pais.

Veamos como se crea el modelo de datos, es decir, las clases que nos van a servir como modelo de las tablas que posteriormente se van a generar en base de datos.

Deberíamos tener estas tres clases en nuestro paquete de modelo.

curso spring boot con angular

Ahora veamos el contenido de cada una de las clases.

Clase Estado

package xyz.yoandroide.persona.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table (name = "estado")
public class Estado {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	@ManyToOne
	@JoinColumn(name="id_pais")
	private Pais pais;
	private String nombre;
	
	public Estado() {
	}
	public Estado(Pais pais, String nombre) {
		super();
		this.pais = pais;
		this.nombre = nombre;
	}
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Pais getPais() {
		return pais;
	}
	public void setPais(Pais pais) {
		this.pais = pais;
	}
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

}

Clase Pais

package xyz.yoandroide.persona.model;

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

@Entity
@Table (name = "pais")
public class Pais {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String nombre;
	
	public Pais() {
	}
	
	public Pais(String nombre) {
		super();
		this.nombre = nombre;
	}
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

}

Clase Persona

package xyz.yoandroide.persona.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table (name = "persona")
public class Persona {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String nombre;
	private String apellido;
	private int edad;
	
	@ManyToOne
	@JoinColumn (name="id_pais")
	private Pais pais;
	@ManyToOne
	@JoinColumn (name="id_estado")
	private Estado estado;

	public Persona(String nombre, String apellido, int edad, Pais pais, Estado estado) {
		super();
		this.nombre = nombre;
		this.apellido = apellido;
		this.edad = edad;
		this.pais = pais;
		this.estado = estado;
	}
	public Persona() {
	}
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	public String getApellido() {
		return apellido;
	}
	public void setApellido(String apellido) {
		this.apellido = apellido;
	}
	public int getEdad() {
		return edad;
	}
	public void setEdad(int edad) {
		this.edad = edad;
	}
	public Pais getPais() {
		return pais;
	}
	public void setPais(Pais pais) {
		this.pais = pais;
	}
	public Estado getEstado() {
		return estado;
	}
	public void setEstado(Estado estado) {
		this.estado = estado;
	}
}

Recordemos que con JPA es necesario tener los getters, setters, contructores con y sin parámetros para que una entidad pueda ser persistida en base de datos.

Las líneas comentadas con un # son para evitar que al detener el servidor borren la base de datos. Lo ideal es que la primera vez que corras el proyecto descomentes las tres ultimas lineas, una vez que te genere la base de datos, vuelves a comentar las ultimas 3 lineas de codigo.

El archivo application.properties nos debió haber quedado algo parecido a este:

spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/persona-db
spring.datasource.username=postgres
spring.datasource.password=
spring.jpa.show-sql=true
#spring.jpa.generate-ddl=true
#spring.jpa.hibernate.ddl-auto=create-drop
#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

Creando repositorios y servicios con Spring Boot

Los repositorios son interfaces que extienden de JPA, interfaces que nos permitirán conectarnos a las funciones necesarias la manipulación de elementos en la base de datos PostgreSQL.

Los servicios son sencillamente la implementación de esas interfaces, en dichas implementaciones nosotros definimos la lógica del negocio.

Repositorios

En la estructura de paquetes nos quedaran tres interfaces como las siguientes:

repositorios curso spring boot y angular

Lo haremos rapido, asi nos deben quedar los repositorios.

package xyz.yoandroide.persona.repository;

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

import xyz.yoandroide.persona.model.Persona;

public interface PersonaRepository extends JpaRepository<Persona, Long>{

}
package xyz.yoandroide.persona.repository;

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

import xyz.yoandroide.persona.model.Pais;

public interface PaisRepository extends JpaRepository<Pais, Long>{

}
package xyz.yoandroide.persona.repository;

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

import xyz.yoandroide.persona.model.Persona;

public interface PersonaRepository extends JpaRepository<Persona, Long>{

}

Notaran que solo es una interface que extiende de JPA, pero en caso de nosotros querer metodos personalizados como filtrar por nombre, apellido o cualquier otro criterio, ahi se declararían la firma de los métodos.

Servicios

Análogamente necesitaremos crear tres servicios que implementen las interfaces antes declaradas.

Seguimos la misma estructura de paquetes.

servicios con spring boot curso gratis angular
package xyz.yoandroide.persona.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import xyz.yoandroide.persona.model.Estado;
import xyz.yoandroide.persona.repository.EstadoRepository;

@Service
public class EstadoService implements EstadoRepository{
	
	@Autowired
	private EstadoRepository estadoRepository;

	@Override
	public List<Estado> findAll() {
		return estadoRepository.findAll();
	}
	
	public List<Estado> findAllByCountry (Long id){
		List<Estado> estadosRespuesta= new ArrayList<>();
		List<Estado> estados= estadoRepository.findAll();
		for (int i=0; i<estados.size(); i++) {
			if (estados.get(i).getPais().getId()==id) {
				estadosRespuesta.add(estados.get(i));
			}
		}
		return estadosRespuesta;
	}

	@Override
	public List<Estado> findAll(Sort sort) {
		return estadoRepository.findAll(sort);
	}

	@Override
	public List<Estado> findAllById(Iterable<Long> ids) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Estado> List<S> saveAll(Iterable<S> entities) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void flush() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public <S extends Estado> S saveAndFlush(S entity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteInBatch(Iterable<Estado> entities) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAllInBatch() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Estado getOne(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Estado> List<S> findAll(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Estado> List<S> findAll(Example<S> example, Sort sort) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Page<Estado> findAll(Pageable pageable) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Estado> S save(S entity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Optional<Estado> findById(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean existsById(Long id) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void deleteById(Long id) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void delete(Estado entity) {
		estadoRepository.delete(entity);
	}

	@Override
	public void deleteAll(Iterable<? extends Estado> entities) {
		estadoRepository.deleteAll(entities);		
	}

	@Override
	public void deleteAll() {
		estadoRepository.deleteAll();
	}

	@Override
	public <S extends Estado> Optional<S> findOne(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Estado> Page<S> findAll(Example<S> example, Pageable pageable) {
		return estadoRepository.findAll(example, pageable);
	}

	@Override
	public <S extends Estado> long count(Example<S> example) {
		return estadoRepository.count(example);
	}

	@Override
	public <S extends Estado> boolean exists(Example<S> example) {
		return estadoRepository.exists(example);
	}
	

}
package xyz.yoandroide.persona.service;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import xyz.yoandroide.persona.model.Pais;
import xyz.yoandroide.persona.repository.PaisRepository;

@Service
public class PaisService implements PaisRepository{
	

	@Autowired
	private PaisRepository paisRepository;

	
	
	@Override
	public List<Pais> findAll() {
		return paisRepository.findAll();
	}

	@Override
	public List<Pais> findAll(Sort sort) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<Pais> findAllById(Iterable<Long> ids) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> List<S> saveAll(Iterable<S> entities) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void flush() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public <S extends Pais> S saveAndFlush(S entity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteInBatch(Iterable<Pais> entities) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAllInBatch() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Pais getOne(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> List<S> findAll(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> List<S> findAll(Example<S> example, Sort sort) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Page<Pais> findAll(Pageable pageable) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> S save(S entity) {
		return paisRepository.save(entity);
	}

	@Override
	public Optional<Pais> findById(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean existsById(Long id) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void deleteById(Long id) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void delete(Pais entity) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAll(Iterable<? extends Pais> entities) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAll() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public <S extends Pais> Optional<S> findOne(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> Page<S> findAll(Example<S> example, Pageable pageable) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Pais> long count(Example<S> example) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public <S extends Pais> boolean exists(Example<S> example) {
		// TODO Auto-generated method stub
		return false;
	}

}
package xyz.yoandroide.persona.service;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import xyz.yoandroide.persona.model.Persona;
import xyz.yoandroide.persona.repository.PersonaRepository;

@Service
public class PersonaService implements PersonaRepository{
	
	
	@Autowired
	private PersonaRepository personaRepository;

	@Override
	public List<Persona> findAll() {
		return personaRepository.findAll();
	}

	@Override
	public List<Persona> findAll(Sort sort) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<Persona> findAllById(Iterable<Long> ids) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> List<S> saveAll(Iterable<S> entities) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void flush() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public <S extends Persona> S saveAndFlush(S entity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void deleteInBatch(Iterable<Persona> entities) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAllInBatch() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Persona getOne(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> List<S> findAll(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> List<S> findAll(Example<S> example, Sort sort) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Page<Persona> findAll(Pageable pageable) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> S save(S entity) {
		return personaRepository.save(entity);
	}

	@Override
	public Optional<Persona> findById(Long id) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean existsById(Long id) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public long count() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void deleteById(Long id) {
		personaRepository.deleteById(id);
	}

	@Override
	public void delete(Persona entity) {
		personaRepository.delete(entity);
	}

	@Override
	public void deleteAll(Iterable<? extends Persona> entities) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void deleteAll() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public <S extends Persona> Optional<S> findOne(Example<S> example) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> Page<S> findAll(Example<S> example, Pageable pageable) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <S extends Persona> long count(Example<S> example) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public <S extends Persona> boolean exists(Example<S> example) {
		// TODO Auto-generated method stub
		return false;
	}
	

}

Creando un API REST con Spring Boot

Finalmente debemos crear nuestro API REST para exponer al exterior nuestro servicio y que agentes externos puedan conectarse y hacer peticiones.

Para el API REST solo vamos a crear una tres clases.

Esta primera clase sera para obtener los estados.

package xyz.yoandroide.persona.rest;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import xyz.yoandroide.persona.model.Estado;
import xyz.yoandroide.persona.service.EstadoService;

@RestController
@RequestMapping ("/estados/")
public class EstadoREST {
	
	@Autowired
	private EstadoService estadoService;
	
	@GetMapping ("{id}")
	private ResponseEntity<List<Estado>> getAllEstadosByPais (@PathVariable("id") Long idPais){
		return ResponseEntity.ok(estadoService.findAllByCountry(idPais));
		
	}

}

La segunda clase será para obtener todos los paises del mundo.

package xyz.yoandroide.persona.rest;

import java.util.List;

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

import xyz.yoandroide.persona.model.Pais;
import xyz.yoandroide.persona.service.PaisService;

@RestController
@RequestMapping ("/pais/")
public class PaisREST {

	
	@Autowired
	private PaisService paisService;
	
	@GetMapping
	private ResponseEntity<List<Pais>> getAllPaises (){
		return ResponseEntity.ok(paisService.findAll());
	}
	
	
}

La tercera clase será todo lo concerniente a la persona.

package xyz.yoandroide.persona.rest;

import java.net.URI;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.RestController;

import xyz.yoandroide.persona.model.Persona;
import xyz.yoandroide.persona.service.PersonaService;

@RestController
@RequestMapping("/personas/")
public class PersonaREST {
	
	@Autowired
	private PersonaService personaService;
	
	@GetMapping
	private ResponseEntity<List<Persona>> getAllPersonas (){
		return ResponseEntity.ok(personaService.findAll());
	}
	
	@PostMapping
	private ResponseEntity<Persona> savePersona (@RequestBody Persona persona){
		try {
			Persona personaGuardada = personaService.save(persona);		
		return ResponseEntity.created(new URI("/personas/"+personaGuardada.getId())).body(personaGuardada);
		} catch (Exception e) {
			return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
		}
	}
	
	@DeleteMapping (value = "delete/{id}")
	private ResponseEntity<Boolean> deletePersona (@PathVariable ("id") Long id){
		personaService.deleteById(id);
		return ResponseEntity.ok(!(personaService.findById(id)!=null));
		
	}

}

Después de haber terminado el backend, nos debió haber quedado la siguiente estructura:

estructura proyecto spring boot CRUD yo androide

Crear un proyecto en Angular

Conectar backend Spring Boot y frontend Angular

Haciendo un refactor al código

Lo que vimos antes fue la forma tradicional de hacer un CRUD, pero ahora te mostrare la forma profesional como se suele hacer en la industria. Estoy seguro que te va a encantar, ya que usaremos menos código, mas pulcro y con mejores practicas de programación.

¿Qué es hacer refactor?

En resumen, es editar un código existente y que funciona. Por lo regular los refactor se hacen para que nuestro código tenga mejores practicas o por alguna actualización ya sea en el framework con el que estemos trabajando o con el lenguaje de programación.

Refactor del backend

Anteriormente se usaba exceso de codigo, por ejemplo para crear getters, setters y constructores. Eso es cosa del pasado con generadores de código como lombok. Lo mismo con el exceso de interfaces que estábamos implementando, esta vez solo implementaremos las necesarias, en lugar de heredarlas.

Veamos como queda nuestro código actualizado.

Cambios en el modelo de datos

Los cambios son muy notables en el modelo de datos, ya que no tendremos esa cantidad de getters, setters y constructores innecesarios.

Al agregar la anotacion @Data estaremos generando de forma automática los getters, setters, constructores y demas elementos necesarios para que el modelo pueda ser persistido en base de datos.

Clase Estado

package xyz.yoandroide.persona.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Data
@Table (name = "estado")
public class Estado implements Serializable {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	@ManyToOne
	@JoinColumn(name="id_pais")
	private Pais pais;
	private String nombre;
}

Clase Pais

package xyz.yoandroide.persona.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Data
@Table (name = "pais")
public class Pais implements Serializable {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String nombre;
}

Clase Persona

package xyz.yoandroide.persona.model;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;

@Entity
@Data
@Table (name = "persona")
public class Persona implements Serializable {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String nombre;
	private String apellido;
	private int edad;
	
	@ManyToOne
	@JoinColumn (name="id_pais")
	private Pais pais;
	@ManyToOne
	@JoinColumn (name="id_estado")
	private Estado estado;
}

Cambios en los servicios

En realidad si no vamos a usar todos los servicios que nos brinda JPA, lo mejor es no implemenar, si no hacer un @autowired a la interface del repositorio.

package xyz.yoandroide.persona.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.yoandroide.persona.model.Estado;
import xyz.yoandroide.persona.repository.EstadoRepository;

import java.util.ArrayList;
import java.util.List;

@Service
public class EstadoService {
	
	@Autowired
	private EstadoRepository estadoRepository;

	public List<Estado> findAll() {
		return estadoRepository.findAll();
	}
	
	public List<Estado> findAllByCountry (Long id){
		List<Estado> estadosRespuesta= new ArrayList<>();
		List<Estado> estados= estadoRepository.findAll();
		for (int i=0; i<estados.size(); i++) {
			if (estados.get(i).getPais().getId()==id) {
				estadosRespuesta.add(estados.get(i));
			}
		}
		return estadosRespuesta;
	}
}
package xyz.yoandroide.persona.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.yoandroide.persona.model.Pais;
import xyz.yoandroide.persona.repository.PaisRepository;

import java.util.List;

@Service
public class PaisService {
	

	@Autowired
	private PaisRepository paisRepository;


	public List<Pais> findAll() {
		return paisRepository.findAll();
	}


}
package xyz.yoandroide.persona.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import xyz.yoandroide.persona.model.Persona;
import xyz.yoandroide.persona.repository.PersonaRepository;

import java.util.List;
import java.util.Optional;

@Service
public class PersonaService {
	
	
	@Autowired
	private PersonaRepository personaRepository;

	public List<Persona> findAll() {
		return personaRepository.findAll();
	}

	public List<Persona> findAll(Sort sort) {
		return personaRepository.findAll(sort);
	}


	public Page<Persona> findAll(Pageable pageable) {
		return personaRepository.findAll(pageable);
	}

	public <S extends Persona> S save(S entity) {
		return personaRepository.save(entity);
	}

	public Optional<Persona> findById(Long id) {
		return personaRepository.findById(id);
	}


	public Boolean deleteById(Long id) {
		if (personaRepository.existsById(id)) {
			personaRepository.deleteById(id);
			return true;
		}
		return false;
	}

	public void delete(Persona entity) {
		personaRepository.delete(entity);
	}


	

}

Vemos que la cantidad de código ha disminuido drásticamente, ahora hay menos código y todo es mas fácil de entender.

Cambios en el POM

Notaremos que lo único diferente aquí es la versión de Spring Boot y se ha agregado la dependencia de lombok.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>xyz.yoandroide.persona</groupId>
	<artifactId>PersonaBACKEND</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>PersonaBACKEND</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>11</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.20</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Refactor del frontend

Para el tema del frontend he agregado Angular Material y unos cuantos validadores en los formularios.

Código fuente refactor

El código fuente de este refactor lo he guardado en una rama diferente, para que quienes apenas este empezando con el curso no pierdan el hilo.

refactor curso de spring boot gratis

Una vez clonado el repositorio, tanto del backend como del front, te cambias de rama a refactor-2021

Si tienes alguna duda sobre el código o si vienes de Youtube, a continuación te dejo los repositorios de GitHub con el código fuente de todo lo que hice durante el curso. Solo debes ir a la pagina siguiente.

Código fuente

Para este tipo de proyectos, lo adecuado es crear dos proyectos diferentes, uno hara el papel de backend y otro sera el frontend. Para el backend usaremos Spring Boot con JPA, PostgreSQL y Rest Api; y para el front usaremos Angular.

Frontend

Este es el archivo de la vista o front. Esta realizado en Angular.

Backend

Este es el archivo del backend. Te recuerdo que para que funcione debes tener instalado PgAdmin o cualquier servidor de PostgreSQL, así como también, modificar el archivo properties que esta en la carpeta de resources.

¿Cómo descargar el código?

Los dos proyectos estan alojados en github, lo cual es simplemente clonar el proyecto, pero si eres nuevo en github o no te interesa usarlo, puedes descargar el proyecto como un archivo comprimido.

descargar-proyecto-crud-angular-y-firebase

Nota: debes tener en cuenta el refactor que se le hizo al código, es decir, el refactor tiene mejores practicas y esta más optimizado.

Recomendado:   Recursividad en Java - ejercicios, ejemplos y teoría
Subir