Enjoy A New Student Discount All 55,000 Courses on sale for Only $12.99

Ends in 05h 23m 49s

Desarrollo de un CRUD con SpringBoot. (Entrada)

En esta etapa, y, después de la creación de todo el CRUD para la tabla Cine, pasamos a definir el CRUD para la tabla Entrada. En este caso vamos a generar alguna validación, definiendo una anotación, y para ello, debemos añadir a nuestro pom.xml una anotacion mas:

Toda las definiciones comunes, como application.properties o secret.properties, los encontrareis en mi github, y esta explicado en el post que os he mencionado anteriormente

Tablas a mantener

Las anotaciones de filtro necesarias

Primero, tenemos que considerar los filtros que necesitamos. Vemos que tenemos que validar el DNI, necesitamos comprobar el formato de una fecha, y que esa fecha no este en el pasado, y que el valor de «entCine» exista en la tabla de cines.

Aunque ya tenemos una clase que se encarga de realizar todos estos filtros, hemos decidido que en lugar de ubicarlos en el controlador o en el servicio, los podríamos hacer por anotaciones, como hacemos con los @NotNull, o @Size…

Para ello, tenemos que crear tres anotaciones, y, para poder comunicar los errores de una forma sencilla, necesitaremos tambien un @ControllerAdvice, y, todo eso lo hemos ido explicando en articulos anteriores

Os remito a todos estos articulos para ver y entender como hemos creado las anotaciones

Para realizar los filtros, en otras ocasiones, hemos utilizado la misma Entity que usábamos para grabar, pero, en este caso, no lo vamos a hacer así, ya que el manejo de fecha nos lo haria un poco retorcido, así que hemos creado la Entity, pero también hemos creado una clase EntradaDTO con la que realizaremos la entrada

EntradaDTO.java

package com.recursosformacion.lcs.model_dto;


import org.springframework.stereotype.Component;

import com.recursosformacion.lcs.util.constraint.interfaces.CheckCineValidation;
import com.recursosformacion.lcs.util.constraint.interfaces.CheckFechaFuturaValidation;
import com.recursosformacion.lcs.util.constraint.interfaces.DniConstraint;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;



@Component
public class EntradaDTO {

	
	private Long id_entrada;
	
	@NotNull
	@CheckFechaFuturaValidation
	private String ent_fecha;
	
	@NotNull(message = "Es necesario indicar el cine")
	@CheckCineValidation
	private Long entCine;
	
	@NotNull(message = "Es necesario indicar la fila")
	@Min(value = 1, message = "Se ha de indicar una fila mayor que 0")
        @Max(value = 100, message = "Se ha de indicar una fila inferior a 100")
	private int ent_fila;
	
	@NotNull(message = "Es necesario indicar el asiento")
	@Min(value = 1, message = "Se ha de indicar un asiento mayor que 0")
        @Max(value = 100, message = "Se ha de indicar un asiento inferior a 100")
	private int ent_numero;

	@NotBlank
	@Size(message = "Error en el identificador del cliente ¿DNI? '${validatedValue}' .Su longitud debe ser {min}",	  max = 12, min = 12)
	@DniConstraint
	private String idCliente;
	

	
	
	public EntradaDTO() {
		super();
	}
	
	public EntradaDTO(Long id_entrada, String ent_fecha, Long id_cine, int ent_numero, String idCliente) {
		super();
		this.id_entrada = id_entrada;
		this.ent_fecha = ent_fecha;
		this.entCine = id_cine;
		this.ent_numero = ent_numero;
		this.idCliente = idCliente;
	}
	public Long getId_entrada() {
		return id_entrada;
	}
	public void setId_entrada(Long id_entrada) {
		this.id_entrada = id_entrada;
	}
	public String getEnt_fecha() {
		return ent_fecha;
	}
	public void setEnt_fecha(String ent_fecha) {
		this.ent_fecha = ent_fecha;
	}
	public Long getId_cine() {
		return entCine;
	}
	public void setId_cine(Long id_cine) {
		this.entCine = id_cine;
	}
	public int getEnt_numero() {
		return ent_numero;
	}
	public void setEnt_numero(int ent_numero) {
		this.ent_numero = ent_numero;
	}
	public String getIdCliente() {
		return idCliente;
	}
	public void setIdCliente(String idCliente) {
		this.idCliente = idCliente;
	}
	
	public Long getEntCine() {
		return entCine;
	}
	public void setEntCine(Long entCine) {
		this.entCine = entCine;
	}
	public int getEnt_fila() {
		return ent_fila;
	}
	public void setEnt_fila(int ent_fila) {
		this.ent_fila = ent_fila;
	}
	@Override
	public String toString() {
		return "EntradaDTO [id_entrada=" + id_entrada + ", ent_fecha=" + ent_fecha + ", id_cine=" + entCine
				+ ", ent_numero=" + ent_numero + ", idCliente=" + idCliente + "]";
	}
	
	
}

Como siempre, he tenido que escribir los atributos y sus filtros, y luego he dejado que Eclipse me genere constructores, getters, setter y toString…

Y con este objeto, recibiremos la información de la entrada, y además ya filtrada, por lo que solo necesitaremos moverla a la Entity y salvarla o actualizarla

La creación de nuestro segundo modelo

Para crear nuestros modelos (Entities), vamos a crear la clase Entrada.java, implementando la interface (Modelo) que preparamos anteriormente

Entrada.java

package com.recursosformacion.lcs.model;

import java.time.LocalDate;

import com.recursosformacion.lcs.model.interfaces.Modelo;
import com.recursosformacion.lcs.util.Constantes;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;


@Entity
@Table(name = "Entrada")
public class Entrada implements Modelo {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id_entrada;
	
	
	@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
	@Column(nullable=false,columnDefinition = "DATE")
	private LocalDate ent_fecha;
	
	@Column(nullable=false)
	private int ent_fila;
	
	@Column(nullable=false)
	private int ent_numero;
	
	@Column(nullable=false)
	private String idCliente;
	 
	private Long entCine;
	
	public Long getEntCine() {
		return entCine;
	}
	public void setEntCine(Long entCine) {
		this.entCine = entCine;
	}
	public Entrada() {
		super();
	}
	public Entrada(long id_entrada, LocalDate ent_fecha, Long entCine, int ent_fila, int ent_numero) {
		super();
		setId_entrada(id_entrada);
		setEnt_fecha(ent_fecha);
		setEntCine(entCine);
		setEnt_numero(ent_numero);
		setEnt_fila(ent_fila);
	}
	

	public Entrada(long id_entrada, String ent_fecha_str,  Long entCine, int ent_fila, int ent_numero) {
		super();
		setId_entrada(id_entrada);
		setEnt_fecha_str(ent_fecha_str);
		setEntCine(entCine);
		setEnt_numero(ent_numero);
		setEnt_fila(ent_fila);
	}
	
	public long getId_entrada() {
		return id_entrada;
	}
	public void setId_entrada(long id_entrada) {
		this.id_entrada = id_entrada;
	}
	public LocalDate getEnt_fecha() {
		return ent_fecha;
	}
	public void setEnt_fecha(LocalDate ent_fecha) {
		this.ent_fecha = ent_fecha;
	}
	
	public int getEnt_numero() {
		return ent_numero;
	}
	public void setEnt_numero(int ent_numero) {
		this.ent_numero = ent_numero;
	}
	public String getIdCliente() {
		return idCliente;
	}
	public void setIdCliente(String idCliente) {
		this.idCliente = idCliente;
	}

	public void setEnt_fecha_str(String ent_fecha_str) {		
		setEnt_fecha(LocalDate.parse(ent_fecha_str,Constantes.FORMATO_FECHA_EU));	
	}
	

	public int getEnt_fila() {
		return ent_fila;
	}
	public void setEnt_fila(int i) {
		this.ent_fila = i;
	}
	@Override
	public String toString() {
		return "Entrada [id_entrada=" + id_entrada + ", ent_fecha=" + ent_fecha + ", Cine=" + entCine + ", ent_numero="
				+ ent_numero + ", idCliente=" + idCliente + "]";
	}
		
	@Override
	public boolean isValidInsert() {
		return true;
	}

	@Override
	public boolean isValidUpdate() {
		return true;
	}
	
}

La única novedad aquí, es que utilizo dos constructores, para poder recibir fecha de cualquier manera, asi como un setter mas, también para soportar el formato de fecha en string.

Tambien señalar que no he añadido mas filtros que los necesarios para crear la tabla, ya que la entrada la realizamos por medio de otro objeto (EntradaDTO)

El repositorio

Como siempre, el repositorio lo haremos implementando una interfaz con JpaRepository, interfaz genérica, que completaremos con la indicación de la Entity a mantener (Entrada), y el tipo de campo del Id, en nuestro caso, Long.

IEntrada.java

Al preparar este repositorio, me encuentro que necesito buscar por cine, y los generadores de Spring no soportan los campos con guion bajo, asi que el campo que antes definia como «ent_cine», pasa a llamarse «entCine. Realmente, solo necesito hacer eso en la Entity, pero para aclarar las cosas, cambio el nombre en todas partes.

De esta forma, en este repositorio, preparo las lecturas para

  • Todas las entradas de un IdCliente
  • Todas las entradas de un entCine

El Servicio

Para esta clase, debo utilizar la interface creada anteriormente para los servicios, y que llamamos IServicio.java, descrita en Desarrollo de un CRUD con SpringBoot. (Cine) y solucionar los metodos de acceso a la BBDD; algo como esto:

EntradaService.java

Quiero señalar que, como en el resto del proyecto, estamos utilizando constructores para recibir las inyecciones de dependencia, según se recomienda en las últimas versiones de Spring.

Tambien recordad que en el articulo que menciono, encontrareis las definiciones de las excepciones que iremos utilizando, para mejorar la claridad de los informes

Por ultimo, y como diferencia, en este servicio tengo que hacer las llamadas que he añadido en el repositorio, indicando lo que hacen, aunque el acceso a tabla lo construirá JPA

El controlador

Hemos hablado de escribir una API que cumpla con los protocolos RESTFUL, por lo que esperaremos recibir información en JSON y devolveremos igualmente la información en ese formato.

Las llamadas que atenderemos, serán:

MétodoURLAcción
GET/api/entradaDevuelve todas las entradas
GET/api/entrada/<id>Devuelve la entrada indicada
GET/api/entrada/leerporcine/<id>Devuelve todas la entradas del cine id
GET/api/entrada/leerporid/<id>Devuelve todas las entradas de un Id de cliente
POST/api/entradaEspera los datos de la entrada en el cuerpo (JSON) y la da de alta en la BBDD
PUT/api/entradaRecibe un objeto EntradaDTO como JSON y actualiza los datos existentes. El id del registro a modificar, lo obtiene del objeto recibido
DELETE/api/entrada/<id>Borra de la tabla el registro indicado por id

La estructura de respuesta podría ser el JSON generado por el objeto a devolver, mas un código http para indicar el resultado, si bien, en el primer ejemplo hemos optado por devolver un JSON con el status, que indica si ha ido bien o no, y como segundo elemento, pueden ir un mensaje, o el dato a devolver en json

Por ejemplo,

o

EntradaController.java

package com.recursosformacion.lcs.controller;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.recursosformacion.lcs.exception.ControllerException;
import com.recursosformacion.lcs.exception.DAOException;
import com.recursosformacion.lcs.exception.DomainException;
import com.recursosformacion.lcs.model.Entrada;
import com.recursosformacion.lcs.model_dto.EntradaDTO;
import com.recursosformacion.lcs.service.CineService;
import com.recursosformacion.lcs.service.EntradaService;

import jakarta.validation.Valid;


@CrossOrigin
@RestController
@RequestMapping("/api/entrada")
public class EntradaController {

	private final EntradaService cDao;

	private final CineService cDaoCine;

	EntradaController(EntradaService cDao, CineService cDaoCine){
		this.cDao = cDao;
		this.cDaoCine = cDaoCine;
	}

	
	@GetMapping("/{id}")
	public ResponseEntity<Map<String, Object>> leerUno(@PathVariable("id") String ids) throws ControllerException {
		String mensaje = "";
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		if (ids != null) {
			try {
				Long id = Long.parseLong(ids);
				Optional<Entrada> entradaDB = (Optional<Entrada>) cDao.leerUno(id);

				if (entradaDB.isPresent()) {
					map.put("status", 1);
					map.put("data", entradaDB.get());
					return new ResponseEntity<>(map, HttpStatus.OK);
				} else {
					mensaje = "No existen datos";
				}
			} catch (NumberFormatException nfe) {
				mensaje = "Formato erroneo";
			}
		} else {
			mensaje = "Formato erroneo";
		}
		throw new ControllerException(mensaje);

	}

	@GetMapping({ "", "/" })
	public ResponseEntity<Map<String, Object>> leerTodos() throws ControllerException {

		Map<String, Object> map = new LinkedHashMap<String, Object>();
		List<Entrada> cat = cDao.listAll();

		if (!cat.isEmpty()) {
			map.put("status", 1);
			map.put("data", cat);
			return new ResponseEntity<>(map, HttpStatus.OK);
		} else {
			throw new ControllerException("No existen datos");

		}
	}

	@GetMapping("/leerporid/{idCliente}")
	public ResponseEntity<Map<String, Object>> leerPorId(@PathVariable("idCliente") String id) throws ControllerException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		List<Entrada> entradas = cDao.findByIdCliente(id);
		if (!entradas.isEmpty()) {
			map.put("status", 1);
			map.put("data", entradas);
			return new ResponseEntity<>(map, HttpStatus.OK);
			
		} else {
			throw new ControllerException("No existen datos");

		}
	}
	@GetMapping("/leerporcine/{idCine}")
	public ResponseEntity<Map<String, Object>> leerporcine(@PathVariable("idCine") Long id) throws ControllerException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		List<Entrada> entradas = cDao.findByEntCine(id);
		if (!entradas.isEmpty()) {
			map.put("status", 1);
			map.put("data", entradas);
			return new ResponseEntity<>(map, HttpStatus.OK);
			
		} else {
			throw new ControllerException("No existen datos para cine " + id);

		}
	}

	@PostMapping
	public ResponseEntity<Map<String, Object>> alta(@Valid @RequestBody EntradaDTO c  ) 
									throws DomainException, ControllerException, DAOException { // ID,NOMBRE,DESCRIPCION
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		Entrada e = convertirDTO(c);
		e.setId_entrada(0l);

		e = cDao.insert(e);
		if (e != null) {
			cDaoCine.addEntrada(e);
			map.put("status", 1);
			map.put("data", e);
			return new ResponseEntity<>(map, HttpStatus.OK);
		} else {
			throw new ControllerException("Error al hacer la insercion");
		}
	}

	@PutMapping
	public ResponseEntity<Map<String, Object>> modificacion(@Valid @RequestBody EntradaDTO c)
			throws ControllerException, DomainException, DAOException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		Entrada e = convertirDTO(c);
		if (cDao.update(e)) {
			map.put("status", 1);
			map.put("message", "Actualizacion realizada");
			return new ResponseEntity<>(map, HttpStatus.OK);
		} else {
			throw new ControllerException("Error al hacer la modificacion");

		}
	}

	@DeleteMapping("/{id}")
	public ResponseEntity<Map<String, Object>> eliminar(@PathVariable("id") String ids) throws ControllerException {
		Map<String, Object> map = new LinkedHashMap<String, Object>();
		if (ids != null) {
			try {
				long id = Long.parseLong(ids);
				Optional<Entrada> entradaDB = cDao.leerUno(id);
				cDao.deleteById(entradaDB.get().getId_entrada());
				map.put("status", 1);
				map.put("message", "Registro borrado");
				return new ResponseEntity<Map<String, Object>>(map, HttpStatus.OK);
			} catch (Exception ex) {
				throw new ControllerException("Error al borrar");

			}
		}
		throw new ControllerException("No existe registro al borrar");
	}

	public Entrada convertirDTO(EntradaDTO d) throws ControllerException {

		Entrada e = new Entrada();
		if (Objects.isNull(d.getId_entrada())) {
			d.setId_entrada(0L);
		}
		e.setId_entrada(d.getId_entrada());
		e.setEnt_fila(d.getEnt_fila());
		e.setEnt_numero(d.getEnt_numero());
		e.setEnt_fecha_str(d.getEnt_fecha());
		e.setIdCliente(d.getIdCliente());
		e.setEntCine(d.getEntCine());
		
		return e;
	}

}

Lo hemos declarado @CrossOrigin para evitar los errores de seguridad, ya que los vamos a autorizar, lo declaramos @RestMapping, porque estamos construyendo una api REST, y señalamos que todos los métodos de este controlador van a responder a «api/entrada» por lo que a nivel de método, obviaremos esta parte del path

Este controlador requiere cDao de Entrada, y CineServicio, ya que deberá añadir la entrada a la lista existente, y solicita que Spring lo inyecte en el constructor, y después vamos definiendo cada método, tras anotar cuando responderá. (Un planteamiento mas purista, desplazaría la modificación de Cine, al servicio de entrada, ya que es el verdadero responsable de la necesidad de esta modificación).(Tambien es cierto que esa modificación es innecesaria…pero estamos enseñando cosas….)

Como veis, en cada método construyo el ResponseEntity que retornara, y que contiene el map con la información correcta. Si tiene que indicar un error, devuelve la excepción con el error correspondiente, y, anteriormente, ya hemos explicado el modulo de tratamiento de errores

Otro tema que debemos tener presente, es que el formato de la entrada no va a ajustarse al objeto «entrada«, ya que tengo fecha que me llegara en formato String, y, para eso, definí un DTO idéntico a Entrada, pero con la fecha en String

Tambien, esa diferencia, hace que necesite un metodo para convertir EntradaDTO a Entrada, aunque mas adelante, lo optimizaremos.

Quedan muchas cosas por hacer, pero en este momento, podríamos ver como va todo, utilizando Postman, y, asi, cerrar esta etapa.

Probando

Recordad que en application.properties establecimos como puerto el 8001, aunque ante cualquier duda, podéis acudir a los mensajes de arranque que aparecen cada vez que modificáis algo, o cuando lanzáis la aplicacion, y hacia el final de todo, a la derecha parece el puerto en que está corriendo el servidor

podemos iniciar Postman y probamos de dar un alta

Vemos que la respuesta es correcta, si damos varias alta, después podemos pedir un listado de todas

Si deseamos modificar un registro, podemos hacer

Y ahora, podemos comprobar como nos ha quedado. Recordad que en mi caso el id es 7952, pero este numero es aleatorio, el vuestro será otro

Supongo que ya sabeis como borrar un elemento, hago un list para verlos, escojo el id que me interesa y …

Si ahora intentáis verlo, recibirías un error, con el mensaje correcto, y si habeis añadido el @ControllerAdvice que os comente en el articulo anterior

Tambien podeis comprobar que si los datos son erróneos…. da error

O la consulta por cine

Conclusión

Todo este desarrollo lo tendreis explicado con mas detalle en youTube, a partir de fin de mayo, y, aunque es conveniente que intentéis escribirlo TODO vosotros, si queréis renunciar a ello, lo teneis tambien en GitHub

Este desarrollo esta hecho para disponer de un fuente para explicar otros temas, tal y como se indica en Visión de conjunto con Spring

Creacion del entity y del DTO

Escribiendo Repositorio, Servicio y controlador

Probando nuestra clase con Postman

Probando EntradaService con @DataJpaTest

Escribiendo el test del controlador con Junit5

Deja un comentario

/*Si te ha gustado el artículo
no dudes en compartirlo*/

Facebook
Twitter
LinkedIn

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Ver mi IP

Ver ip de mi máquina
tipo valor
Ip: 3.237.15.145
Proxy: 3.237.15.145
Remote host: ec2-3-237-15-145.compute-1.amazonaws.com
Remote port: 37230
** 3.237.15.145, 172.70.34.142