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.
- ¿Qué significa CRUD en desarrollo de software?
- Herramientas necesarias
- Stack tecnológico
- Documentación oficial
- Creando un proyecto en Spring Boot
- Creando modelo de datos
- Creando repositorios y servicios con Spring Boot
- Creando un API REST con Spring Boot
- Crear un proyecto en Angular
- Conectar backend Spring Boot y frontend Angular
- Haciendo un refactor al código
- Código fuente refactor
- Código fuente
¿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.
Aunque este post y la serie de videos de Youtube fueron grabadas hace algunos años, todos los proyectos han sido actualizados a la ultima versión de Spring Boot y Angular, por lo cual, algunas cosas pueden que hayan cambiado.
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.
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:
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.
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:
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.
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.
Puedes revisar también el script para insertar los países y ciudades en la base de datos.
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.
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.
- Cómo Dockerizar una Aplicación Spring Boot con Kotlin: Guía Paso a PasoDockerizar tu backend, microservicio o aplicación en Spring Boot y con lenguaje…
- Java + Spring Boot Semi Senior: Lo que deberías saberHace un tiempo grabé un video sobre lo que deberías saber para…
- Agregar Actuator + Grafana + Prometheus a Spring BootEn este post veremos como configurar Actuator, Prometheus y Grafana para moitorear…
- CRUD con Spring Boot + JPA + PostgreSQL + Pagination + Validator + SwaggerEn este post les mostrare como realizar un CRUD en Spring Boot,…
- Lombok y Spring Boot: Agregar y configurar desde ceroEn este post te enseñare como agregar y configurar Lombok en un…
- Curso completo de SPRING BOOT CRUD + JPA + POSTGRESQL +REST + AngularAngular y Spring Boot es una poderosa combinación, muchas empresas están haciendo…
- Configuración de CORS en un proyecto Spring BootEn este post tratare de ser lo más breve posible. Veremos como…
- Como hacer un CRUD en Spring Boot + JPA + PostgreSQL + API RESTEn este tutorial te explico como hacer un API REST completo desde…
Si quieres conocer otros artículos parecidos a Curso completo de SPRING BOOT CRUD + JPA + POSTGRESQL +REST + Angular puedes visitar la categoría Cursos.
Entradas relacionadas