---
title: 'Guía de Desarrollo del Back'
---
Guía de Desarrollo del Back
===
* **Coordinador Tecnológico:** Rafael Palau
* **Arquitectos:** Horacio Nemeth, Marcelo Lopez
* **Consultores:**
Ilse Grau
Julio Mello
Marcos Benítez
Marco Aquino
Lauro Segovia
## Contenido
[TOC]
## Introducción
En esta guía se describen los conceptos esenciales del framework Spring/Boot y los pasos necesarios para la creación de un servicio REST de tipo Alta, Baja Modificación (ABM) en el proyecto Backend utilizando la estructura propuesta.
Para el caso práctico de la presente guía se utilizará la entidad de *FRECUENCIAS*.
## Objetivo
* Introducir al desarrollador en los conceptos fundamentales del framework Angular
* Guiar al desarrollador en la creación de los archivos necesarios para un ABM.
## Prerequisitos
* Tener instalado el IDE Eclipse/Spring Tool Suite
* Tener instalado Java JDK en su versión 11
* Tener instalado Maven
* Tener instalado Git
## Resultados esperados
Servicio correspondiente a la tabla Frecuencias desarrollado y expuesto para ser utilizado.
## Concepto Spring
---
:::info
En esta sección se presentará un resumen de los conceptos fundamentales del framework Spring.
Para ahondar más al respecto es conveniente consultar las documentación oficial <a href="https://spring.io/projects/spring-framework" target="_blank"> Documentación Spring</a>
:::
Spring comenzó como una alternativa ligera a Java Enterprise Edition (JEE). En lugar de desarrollar componentes Enterprise complejos utilizando JavaBeans (EJBs), Spring ofrecía un enfoque más sencillo para el desarrollo de Java Enterprise, utilizando la inyección de dependencias (DI) y la programación orientada a aspectos (AOP) para conseguir prestaciones similares a los EJB empleando objetos Java simples (POJOs).
Spring ofrece un conjunto de módulos que suplen el proceso de construcción de una aplicación Empresarial como ser: persistencia de datos, seguridad,microservicio, web MVC, etc.
#### Módulos

Figura : Diagrama de los principales módulos del Spring
Históricamente, la forma de configurar el contexto de la aplicación del Spring para vincular los beans se requería agregar uno o varios archivos XML que describan la relación y dependencia entre los componentes. Esto con los años fue reemplazado por las configuraciones basadas en Java (Java-based configuration).
#### Principales anotaciones
##### Gestión de componentes
| Anotaciones | Descripción |
| -------- | -------- |
| @Autowired | Se aplica a campos y métodos. La misma inyecta la dependencia del objeto implícitamente |
| @Configuration | Se aplica a una clase para agregar configuración mediante clase java y tendrá métodos para crear instancias y configurar las dependencias |
| @Bean | Se utiliza a nivel de método de clase, la anotación @Bean funciona con @Configuration para crear beans en el contexto de la aplicación Spring |
| @Value | Se utiliza a nivel de campo, parámetro de constructor y parámetro de método. Indica una expresión que agrega valor a la clase desde una archivo de propiedades o desde el Spring Config |
| @Controller | Se usa para indicar que la clase es un controlador para el Spring MVC |
| @Service | Se aplica a nivel de clase, generalemente las clases anotadas con @Service realizan algún servicio, como ejecutar lógica de negocios, realizar cálculos y llamar a API externas |
| @Repository | Se aplica a nivel de clase, generalemente estas clases anotadas con @Repository permiten el acceso a la base de datos |
##### Gestión de métodos HTTP
| Anotaciones | Método HTTP | Uso |
| -------- | -------- | -------- |
| @GetMapping | HTTP GET requests | Para leer datos |
| @PostMapping | HTTP POST requests | Para crear datos |
| @PutMapping | HTTP PUT requests | Para actualizar datos |
| @DeleteMapping | HTTP DELETE requests | Para eliminar datos |
| @RestController | Anotación para gestionar las respuesta en el *body* del *response* de todos los métodos HTTP | |
| @RequestMapping | Anotación de propósito general para el manejo de métodos HTTP | |
| @RequestParam |Anotación que es utilizado para anotar argumentos del método del controlador, el mismo es enviado como query params del recurso solicitado | |
| @PathVariable |Anotación que es utilizado para anotar argumentos del método del controlador, el mismo es enviado como parte del recurso solicitado . | |
### Spring Boot
Con Spring Boot se simplifico el desarrollo utilizando Spring por las siguientes razones :
* Configuración automática : Spring Boot provee configuraciones comunes y esenciales para cualquier aplicación utilizando el framework Spring.
* Dependencias iniciales : Spring Boot provee funcionalidad para la construcción de proyectos base con las dependencias requeridas por las aplicaciones Spring.
* Interfaz de linea de comando (CLI) : Spring Boot provee un conjunto de comandos que facilitan la creación y construcción de proyectos Spring.
* Monitoreo (Actuator) : Provee servicio para consultar el estado actual de la aplicación Spring en ejecución.
#### Principales anotaciones
* @EnableAutoConfiguration
Esta anotación se utiliza en la clase principal de la aplicación. Esta anotación le indica al Spring Boot que comience a agregar beans según la configuración de classpath.
* @SpringBootApplication
Esta anotación se utiliza en la clase principal de la aplicación.Esta anotación se compone de las siguientes anotaciones:
* @SpringBootConfiguration
* @EnableAutoConfiguration
* @ComponentScan
## Estructura de proyecto
El proyecto creado utiliza como gestor de dependencias el [Maven](https://maven.apache.org/), la misma se compone de un proyecto padre de y varios sub-módulos.
> El módulo *siare-common* contiene todas las clases comunes que son utilizadas por los otros módulos del proyecto.
* Ejemplo :

Figura : Estructura del proyecto
Cada módulo cuenta con la siguiente estructura
| Directorio/Archivo | Descripción|
| -------- | -------- |
| src/main/java | paquete que contiene los archivos fuentes del módulo |
| src/main/resources | paquete que contiene los archivos de configuración del módulo |
| src/test/java | paquete que contiene los archivos fuentes de las pruebas unitarias |
| target | Directorio que contiene los archivos comprimidos generados con Maven jar,war,ear etc |
| pom.xml | Archivo que contiene la definición del módulo y sus dependencias |
### Esquema de módulo

## Git
Git es un sistema de control de versiones de código. Las características del Git son las siguientes :
* Código abierto.
* Gratuito.
* Diseñado para manejar proyectos grandes y pequeños.
* Fácil de aprender.
* Ocupa poco espacio con un rendimiento ultrarrápido.
## Importación del Proyecto desde GitLab
Para proceder a clonar el proyecto Backend primeramente se debe instalar git, crear una rama propia del proyecto e importarlo como proyecto maven en el STS.
### Instalación de Git
Descargar el instalador del git (Windows installer).
https://git-scm.com/downloads
Realizar doble click sobre el instalador y seguir las instrucciones.
### Clonar el Proyecto Backend
* Una vez instalado el git se procede ha ubicarse en la carpeta donde se desea clonar el proyecto y hacer **click derecho --> Git Bash Here** y despliega la ventana de comando de git.

**Figura.** Ejecutar git bash.

**Figura.** Editor vim de git.
* Configurar la variables globales del git
:::info
git config --global user.name "nombre de usuario"
git config --global user.email "user@hacienda.gov.py"
git config --global http.sslVerify false
:::

**Figura.** Configuración de variables.
* Clonar proyecto.
Antes de proceder a clonar el proyecto se debe acceder a GitLab del proyecto para copiar la url del proyecto.
1. Acceder al GitLab del proyecto mediante las credenciales válidas (username, password).
:::info
https://gitlab.siare.gov.py/
:::

**Figura.** Acceder al GitLab del proyecto.
### Flujo del Git
## Creación de ABM
:::info
Se asume que el proyecto fue creado y configurado
:::
### Diagrama del ABM

Figura : Diagrama del ABM de Frecuencias
> Los gráficos coloreados son los archivos requeridos para completar el proceso de desarrollo de un ABM genérico
### Paso 1 - Creación de la entidad
* Ingresar al Spring Tool Suite
* Ir al paquete *py.gov.siare.presupuesto.model* y crear una clase java con el nombre de *Frecuencias* esta clase debe extender de la clase base *BaseEntity*.
> La clase base es utilizada para agrupar los atributos comunes
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import lombok.Data;
import py.gov.siare.siarecommon.model.BaseEntity;
@Entity
@Data
@Table(name = "frecuencias")
@SequenceGenerator(sequenceName = "SEQ_FRECUENCIAS_ID",
allocationSize = 1, name = "baseGen")
public class Frecuencias extends BaseEntity {
private String nombre;
private String abreviatura;
}
```
> El archivo contiene una clase simple Java con anotaciones propias del JPA
> Los atributos de la clase *Frecuencias* son : nombre de tipo String y abreviatura de tipo String.
> Los atributos heredados mas importantes de la clase *Frecuencias*: son id, usuarioCreador, usuarioModificador, activo y borrado. Que son heredados de la clase base *BaseEntity*.
> No se han agregado el constructor y los metodos setter y getter por contar con la anotacion **@Data** que es parte de la liberia [Lombok](https://projectlombok.org/), el cual crear los códigos necesarios en tiempo de compilación.
> La clase también esta anotada con la anotacion **@Entity**, el cual indica al JPA que la clase Java debe tratarse como una entidad, para indicar el nombre de la tabla asociada a la entidad en la clase se utiliza la anotación **@Table**.
> La clase *Frecuencias*: debe tener las siguientes anotaciones @SequenceGenerator, @Data, @Table(name = "frecuencias") para indicarle al JPA como será generado el identificador del Objeto.
> Las propiedades *nombre* y *abreviatura* no cuenta con la anotación **@Column** ya que tiene el mismo nombre de la columna definida en la tabla de la base de datos.
### Paso 2 - Creación del DTO
* Ir al paquete *py.gov.siare.presupuesto.dto* y crear una clase java con el nombre de *FrecuenciasDto* esta clase debe extender de la clase base *BaseDto*.
> La clase base es utilizada para agrupar los atributos comunes
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import py.gov.siare.siarecommon.dto.BaseDto;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FrecuenciasDto extends BaseDto {
private Long id;
private String abreviatura;
private String nombre;
}
```
> El archivo contiene una clase simple Java con anotaciones
> Los atributos de la clase *FrecuenciasDto* son : id de tipo Long, nombre de tipo String y abreviatura de tipo String.
> No se han agregado el constructor y los metodos setter y getter por contar con la anotacion **@Data** que es parte de la liberia [Lombok], el cual crear los códigos necesarios en tiempo de compilación.
> La clase cuenta ademas con la anotación **@JsonInclude(JsonInclude.Include.NON_NULL)**, el cual indica a la libreria jackson (procesador de Objeto/Json - Json/Objeto) que no agregue los atributos nulos al objeto generado.
### Paso 3 - Creación del repositorio
* Ir al paquete *py.gov.siare.presupuesto.repository* y crear una interfaz java con el nombre de *FrecuenciasRepository*, la misma debe extender de las siguientes interfaces *JpaRepository* y *JpaSpecificationExecutor*.
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import py.gov.siare.presupuesto.model.Frecuencias;
public interface FrecuenciasRepository extends
JpaRepository<Frecuencias, Long>,
JpaSpecificationExecutor<Frecuencias> {
}
```
> En la interfaz se ha extendido de *JpaRepository* el cual recibe dos parámetros genéricos que son la clase entidad *Frecuencias* y el tipo de dato *Long* que coincide con el *ID* de la entidad. Así también se extiende de la interfaz *JpaSpecificationExecutor* el cual recibe el parámetro genérico de la clase entidad *Frecuencias*
> Al extender de estas dos intefaces la interfaz *FrecuenciasRepository* hereda varios métodos para persitir datos de la entidad *Frecuencias*, así también los métodos de guardar, eliminar, actualizar y obtener registros.
> Adicionalmente Spring Data JPA agrega métodos dinámicos de búsqueda por nombre de atributos como ser *findByNombre* o *findByAbreviatura*
> Generalmente en una aplicación Java se debería definir la implementación de la interfaz *FrecuenciasRepository*, pero gracias al Spring Data JPA no es necesario ya que el Spring crea la implementación en tiempo de compilación.
### Paso 4 - Creación del servicio
> Ir al paquete *py.gov.siare.presupuesto.service* y crear una interfaz java con el nombre de *FrecuenciasService*, la misma debe extender de la interfaz *ServiceBase*.
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.service;
import py.gov.siare.presupuesto.dto.FrecuenciasDto;
import py.gov.siare.presupuesto.model.Frecuencias;
import py.gov.siare.siarecommon.service.ServiceBase;
public interface FrecuenciasService
extends ServiceBase<Frecuencias, Long, FrecuenciasDto>{
}
```
> En la interfaz se ha extendido de *ServiceBase* el cual recibe tres parámetros genéricos que son la clase entidad *Frecuencias* y el tipo de dato *Long* que coincide con el *ID* de la entidad y la clase DTO *FrecuenciasDto*.
> La interfaz *ServiceBase* contiene los métodos comunes para un ABM simple
### Paso 5 - Creación de la implementación del servicio
* Ir al paquete *py.gov.siare.presupuesto.service.impl* y crear una clase java con el nombre de *FrecuenciasServiceImpl*, la misma debe extender de la clase *ServiceBaseImpl*.
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.service.impl;
import py.gov.siare.presupuesto.dto.FrecuenciasDto;
import py.gov.siare.presupuesto.model.Frecuencias;
import py.gov.siare.presupuesto.repository.FrecuenciasRepository;
import py.gov.siare.presupuesto.service.FrecuenciasService;
import py.gov.siare.siarecommon.dto.ResponseDto;
import py.gov.siare.siarecommon.enumerated.Mensaje;
import py.gov.siare.siarecommon.service.impl.ServiceBaseImpl;
@Service // Anotación propia del Spring para los servicios
public class FrecuenciasServiceImpl
extends ServiceBaseImpl<Frecuencias, Long, FrecuenciasDto>
implements FrecuenciasService {
}
```
> Como se puede apreciar la clase esta anotada con la anotación @Service con el proposito de indicar al Spring que trate la clase como un componente de servicio al momento de escanear y construir la aplicación.
> La clase extiende de *ServiceBaseImpl* la misma contiene implementación de los métodos comunes de la interfaz *ServiceBase*
> La clase *ServiceBaseImpl* requiere de la redefinición de los métodos : *obtenerModelMapper*, *obtenerClaseEntidad*, *obtenerClaseDTO* y *obtenerObjectMapper* para poder realizar las operaciones comunes. Así también es necesario proveerle la instancia del repositorio *FrecuenciasRepository*.
### Paso 6 - Creación del Controller
* Ir al paquete *py.gov.siare.presupuesto.controller* y crear una clase java con el nombre de *FrecuenciasController*, la misma debe extender de la clase *ControllerBase*.
* El código tendrá el siguiente contenido:
```java=
package py.gov.siare.presupuesto.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import py.gov.siare.presupuesto.dto.FrecuenciasDto;
import py.gov.siare.presupuesto.model.Frecuencias;
import py.gov.siare.siarecommon.controller.ControllerBase;
@RestController
@RequestMapping({ "frecuencias" })
public class FrecuenciasController
extends ControllerBase<Frecuencias, Long, FrecuenciasDto>{
}
```
> Como se puede apreciar la clase esta anotada con la anotación @RestController con el proposito de indicar al Spring que trate la clase como un componente de Controller al momento de escanear y construir la aplicación. Así también cuenta con la anotación @RequestMapping con el parametro *{ "frecuencias" }* que indica al Spring el *path* del controlador a ser creado.
> La clase extiende de *ControllerBase* la misma contiene implementación de los métodos comunes requeridos para un controlador.
> La clase *ControllerBase* requiere de tres parámetro ya que la misma es una clase genérica por lo tanto se debe agregar la referencia de la clase *Frecuencias*, *Long* y *FrecuenciasDto*, así también es necesario redefinir el método de obtención de servicio a ser utilizado por la clase base.
### Paso 7 - Validaciones (Personalizadas) en la implementación del servicio
Para realizar validaciones previamente a las operaciones de creación, actualización y eliminación (lógica) se deben reescribir los métodos de la clase *ServiceBase*:
```java=
default Boolean validarCreacion(T entity, ResponseDto<E> respuesta) {
return true;
}
default Boolean validarActualizacion(T entity, ResponseDto<E> respuesta) {
return true;
}
default Boolean validarEliminacion(T entity, ResponseDto<E> respuesta) {
return true;
}
```
Estos métodos reciben como parámetros el objeto de la entidad *entity* sobre la cual se realizará la operación y una instancia de la respuesta *respuesta* que se devolverá con el resultado de la operación.
A continuación se muestra un ejemplo de validación previa a la creación de un registro que verifica la existencia de registros con el mismo valor en el campo *descripción*:
```java=
@Override
public Boolean validarCreacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) {
// validar descripcion duplicada
if (countDescripcionDuplicada(entity) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR
.getMensajeDetail("Descripción duplicada"));
return false;
}
return true;
}
```
Para interrumpir la operación correspondiente, en caso de incumplimento de alguna validación, el método debe ser reescrito y retornar el valor booleano *false* y registar el mensaje a desplegar en la respuesta.
Dentro de estos métodos se pueden incluir validaciones que requieran consultar otras entidades, como por ejemplo, esta valiación previa a la eliminación que verifica otra entidad asociada a la misma:
```java=
@Override
public Boolean validarEliminacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) {
// validar bienes asociados
if (countBienesAsociados(entity) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR
.getMensajeDetail("El Tipo de Arrendamiento posee Bien/es asociado/s"));
return false;
}
return true;
}
```
Tambien se pueden incluir multiples validaciones en serie:
```java=
@Override
public Boolean validarActualizacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) {
ArrendamientoTipos entityActual = repo.getById(entity.getId());
// validar descripcion duplicada
if (countDescripcionDuplicada(entity) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail("Descripción duplicada"));
return false;
}
// si se inactiva registro validar bienes asociados
if (!entity.getActivo() && entityActual.getActivo() && countBienesAsociados(entity) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR
.getMensajeDetail("El Tipo de Arrendamiento posee Bien/es asociado/s"));
return false;
}
// si se restablece registro validar descripcion duplicada
if (!entity.getBorrado() && entityActual.getBorrado() && countDescripcionDuplicada(entity) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail("Descripción duplicada"));
return false;
}
return true;
}
```
La definición de los métodos auxiliares utilizado para la validaciones:
```java=
private Long countDescripcionDuplicada(ArrendamientoTipos entity) {
Specification<ArrendamientoTipos> descripcionDuplicada = new Specification<ArrendamientoTipos>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<ArrendamientoTipos> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.and(criteriaBuilder.notEqual(root.get("id"), entity.getId()),
criteriaBuilder.equal(root.get("descripcion"), entity.getDescripcion().trim()),
criteriaBuilder.equal(root.get("borrado"), false));
}
};
return repo.count(descripcionDuplicada);
}
private Long countBienesAsociados(ArrendamientoTipos entity) {
Bienes bien = new Bienes();
bien.setArrendamientoTipo(entity);
bien.setBorrado(false);
return repoBienes.count(Example.of(bien));
}
```
### Paso 8 - Validaciones (Genéricas) vía aspectos
El proceso de validación genérica esta enfocado a la verificación de atributos duplicados y eliminación lógica.
Para cada verificación es utilizado anotaciones de Java y aspecto del Spring, que generalizan y externalizan la tarea de la gestión de validación a nivel de Objeto DTO y Entidad relacional (Entity).
* Clases que componen la validación con aspectos
* CheckValidation
* ConstraintsUnique
* Editar
* Guardar
* ValidarEliminacionLogica
* EliminacionAspect
* ValidadorCampoUnicoAspect
#### Puntos de corte de los aspecto
Los aspectos cuenta con un punto de corte que estan definidos vía anotaciones a nivel de métodos.
La clase donde es definida los puntos de corte es : *ServiceBaseImpl*
> Operación de **guardar**
```java
@CheckValidation // punto de corte vía anotación
@Guardar // anotación para determinar la operación del método
public ResponseDto<E> guardar(E entidadDTO) {
ResponseDto<E> respuesta = new ResponseDto<>();
Class<?> clazz = this.obtenerClaseEntidad();
T entidad = (T) this.obtenerModelMapper().map(entidadDTO, clazz);
if (Boolean.TRUE.equals(validarCreacion(entidadDTO, respuesta))) {
entidad = repository.save(entidad);
respuesta = ResponseBuilder.crearObjetoRespuesta(entidad, this.obtenerClaseDTO());
}
return respuesta;
}
```
> Operación de **actualización**
```java
@CheckValidation // punto de corte vía anotación
@Editar // anotación para determinar la operación del método
public ResponseDto<E> actualizar(K id, E entidadDTO) {
AtomicReference<ResponseDto<E>> respuesta = new AtomicReference<>();
Class<E> clazzDTO = this.obtenerClaseDTO();
Optional<T> entidad = repository.findById(id);
if (Boolean.TRUE.equals(validarActualizacion(entidadDTO, respuesta.get()))) {
entidad.ifPresent(item -> {
T entidadParaActualizar = this.actualizacionSelectivaPorCampo(item, entidadDTO);
this.repository.save(entidadParaActualizar);
respuesta.set(ResponseBuilder.crearObjetoRespuesta(entidadParaActualizar, clazzDTO));
});
}
return respuesta.get();
}
```
> Operación de **eliminación**
```java
@ValidarEliminacionLogica
public ResponseDto<E> eliminarPorEntidad(E entidadDTO) {
ResponseDto<E> respuesta = new ResponseDto<>();
Class<E> clazzDTO = this.obtenerClaseDTO();
Class<T> clazzEntidad = this.obtenerClaseEntidad();
if (entidadDTO != null) {
T registro = this.obtenerModelMapper().map(entidadDTO, clazzEntidad);
if (Boolean.TRUE.equals(validarEliminacion(registro, respuesta))) {
agregarValorAEntidadPorNombre(registro, "borrado", true, clazzEntidad);
this.repository.save(registro);
respuesta = ResponseBuilder.crearObjetoRespuesta(entidadDTO, clazzDTO);
}
}
return respuesta;
}
```
:::info
> Para la operación de actualización y guardado es necesario indicar vía anotación cuales son los atributos a ser evaluados en el proceso de validación.
> Para la operación de eliminación el comportamiento esta definido por defecto.
:::
#### Control de campos duplicados
> La anotación a ser utilizada para el control de campos únicos es @ConstraintsUnique, está anotación es utilizada a nivel de atributo del de la clase que extiende de BaseDto.java. La anotación de unique permite definir agrupaciones por nombre para la validación de campos
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface ConstraintsUnique {
/**
* Es el nombre del atributo a validar este valor es utilizado para generacion
* del objeto CriterioFiltro es importante que este nombre coincida con el
* atributo del objeto de dominio (Entity)
*
* @see CriterioFiltro
*/
String atributo() default "";
/**
*
* Código del mensaje definido dentro del archivo messages.properties
*/
String mensaje() default "{default.not.unique.message}";
/**
*
* valor de agrupación de campos para la validación por defecto se evalua campo
* por campo
*/
boolean agrupar() default false;
/**
*
* Atributo utilizado para la clasificacion de la agrupacion por atributos
*/
String alias() default "";
}
```
#### Paso refactorización de las clases serviceImpl con aspectos
> Ejemplo :
> Para el caso se toma como referencia la clase BancoSucursalesDto
* Ir a la clase DTO y agregar la anotación **@ConstraintsUnique** a nivel de propieda
```java
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BancoSucursalesDto extends BaseDto implements Serializable{
private Long id;
@ConstraintsUnique(mensaje = "{uniqueAgrupado.not.unique.message}", agrupar = true, alias = "codigoSiaf")
private BancosDto bancos;
@Size(min = 1, max = 50, message = "{default.invalid.size.message}")
@NotBlank(message = "{default.blank.message}")
@ConstraintsUnique(mensaje = "{default.not.unique.message}")
@UpperCase
private String nombre;
@Size(max = 3, message = "{default.invalid.size.message}")
@ConstraintsUnique(agrupar = true, mensaje = "{uniqueAgrupado.not.unique.message}")
@UpperCase
private String codigoBicSwift;
@Size(max = 8, message = "{default.invalid.size.message}")
@ConstraintsUnique(agrupar = true, mensaje = "{uniqueAgrupado.not.unique.message}")
@UpperCase
@NotNull(message = "{default.null.message}")
private String codigoBicSwiftBanco;
@ConstraintsUnique(mensaje = "{uniqueAgrupado.not.unique.message}", agrupar = true, alias = "codigoSiaf")
@Min(value = 1, message = "{default.invalid.min.message}")
@NotNull(message = "{default.null.message}")
private Long codigoSiaf;
}
```
---
* Ir a la clase BienEstadosServiceImpl y quitar la implementación de los métodos de :
:::warning
Estos métodos son necesarios quitarlos del servicio ya que el aspecto (@ValidarEliminacionLogica, @CheckValidation) actualmente realiza las operaciones de validación de campo/s unico y eliminación
:::
> **validarCreacion**
```java
@Override
public Boolean validarCreacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) {
// validar descripcion duplicada
if (countNombreDuplicada(entityDto) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(
this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es"))));
return false;
}
return true;
}
```
> **validarActualizacion**
```java
@Override
public Boolean validarActualizacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) {
// validar descripcion duplicada
if (countNombreDuplicada(entityDto) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(
this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es"))));
return false;
}
return true;
}
```
> **validarEliminacion**
```java
@Override
public Boolean validarEliminacion(BienEstados entityActual, ResponseDto<BienEstadosDto> respuesta) {
// validar estado bien asociados en comites
if (countEstadoBienEnComites(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
// validar estado bien asociados en comites
if (countEstadoBienEnRAF(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
// validar estado bien asociados en comites
if (countEstadoBienEnRCF(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
return true;
}
```
> Clase antes del cambio :
```java
public class BienEstadosServiceImpl extends ServiceBaseImpl<BienEstados, Long, BienEstadosDto>
implements BienEstadosService {
private static final String NOMBRE = "nombre";
private static final String INHABILITADO = "inhabilitado";
private static final String TEXTO_RELACIONADO = "bienestado.related.value.message";
@Autowired
private BienEstadosRepository repo;
@Autowired
private ComitesRepository comitesRepository;
@Autowired
private ActaRecepcionFirmasRepository actaRecepcionFirmasRepository;
@Autowired
private RecepcionComprobanteFirmasRepository recepcionComprobanteFirmas;
@Autowired
private MessageSource mensajes;
@Autowired
ModelMapper modelMapper;
@Autowired
ObjectMapper objectMapper;
@Override
public JpaRepository<BienEstados, Long> obtenerRepository() {
return this.repo;
}
@Override
public ModelMapper obtenerModelMapper() {
return this.modelMapper;
}
@Override
public Class<BienEstados> obtenerClaseEntidad() {
return BienEstados.class;
}
@Override
public Class<BienEstadosDto> obtenerClaseDTO() {
return BienEstadosDto.class;
}
@Override
public ObjectMapper obtenerObjectMapper() {
return this.objectMapper;
}
@Override
public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNull() {
List<BienEstados> listado = this.repo.findByBienEstadosIsNull();
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
@Override
public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNotNull() {
List<BienEstados> listado = this.repo.findByBienEstadosIsNotNull();
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosPorPadreId(Long id) {
List<BienEstados> listado = this.repo.findAllByBienEstadosId(id);
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosActivosPorPadreId(Long id) {
List<BienEstados> listado = this.repo.findAllActivosNoBorradosByBienEstadosId(id);
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
@Override
public Boolean validarCreacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) {
// validar descripcion duplicada
if (countNombreDuplicada(entityDto) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(
this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es"))));
return false;
}
return true;
}
@Override
public Boolean validarActualizacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) {
// validar descripcion duplicada
if (countNombreDuplicada(entityDto) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(
this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es"))));
return false;
}
return true;
}
private Long countNombreDuplicada(BienEstadosDto entity) {
Specification<BienEstados> descripcionDuplicada = new Specification<BienEstados>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<BienEstados> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
if (entity.getId() == null)
return criteriaBuilder.and(criteriaBuilder.equal(root.get(NOMBRE), entity.getNombre().trim()),
criteriaBuilder.equal(root.get("borrado"), false));
else
return criteriaBuilder.and(criteriaBuilder.notEqual(root.get("id"), entity.getId()),
criteriaBuilder.equal(root.get(NOMBRE), entity.getNombre().trim()),
criteriaBuilder.equal(root.get("borrado"), false));
}
};
return repo.count(descripcionDuplicada);
}
@Override
public Boolean validarEliminacion(BienEstados entityActual, ResponseDto<BienEstadosDto> respuesta) {
// validar estado bien asociados en comites
if (countEstadoBienEnComites(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
// validar estado bien asociados en comites
if (countEstadoBienEnRAF(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
// validar estado bien asociados en comites
if (countEstadoBienEnRCF(entityActual) > 0) {
respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes
.getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es"))));
return false;
}
return true;
}
/**
* Obtiene una cantidad de registros de Comites que poseen un estado bien que se
* provee por parametro
*
* @param entityActual -> Objeto BienEstado que condiciona la busqueda
* @return cantidad de registros que cumplen con el criterio de busqueda
*/
private int countEstadoBienEnComites(BienEstados entityActual) {
List<Comites> lista = comitesRepository.listarComitesByBienEstadoId(entityActual.getId());
return lista.size();
}
/**
* Obtiene una cantidad de registros de Recepcion_Actas_Firmas que poseen un
* estado bien que se provee por parametro
*
* @param entityActual -> Objeto BienEstado que condiciona la busqueda
* @return cantidad de registros que cumplen con el criterio de busqueda
*/
private int countEstadoBienEnRAF(BienEstados entityActual) {
List<ActaRecepcionFirmas> lista = actaRecepcionFirmasRepository
.listarActaRecepcionFirmasByBienEstadoId(entityActual.getId());
return lista.size();
}
/**
* Obtiene una cantidad de registros de Recepcion_Comprobante_Firmas que poseen
* un estado bien que se provee por parametro
*
* @param entityActual -> Objeto BienEstado que condiciona la busqueda
* @return cantidad de registros que cumplen con el criterio de busqueda
*/
private int countEstadoBienEnRCF(BienEstados entityActual) {
List<RecepcionComprobanteFirmas> lista = recepcionComprobanteFirmas.listaRCFByEstadoBien(entityActual.getId());
return lista.size();
}
}
```
> Clase luego del cambio
```java
public class BienEstadosServiceImpl extends ServiceBaseImpl<BienEstados, Long, BienEstadosDto>
implements BienEstadosService {
@Override
public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNull() {
List<BienEstados> listado = ((BienEstadosRepository) repository).findByBienEstadosIsNull();
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
@Override
public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNotNull() {
List<BienEstados> listado = ((BienEstadosRepository) repository).findByBienEstadosIsNotNull();
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosPorPadreId(Long id) {
List<BienEstados> listado = ((BienEstadosRepository) repository).findAllByBienEstadosId(id);
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosActivosPorPadreId(Long id) {
List<BienEstados> listado = ((BienEstadosRepository) repository).findAllActivosNoBorradosByBienEstadosId(id);
ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>();
responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO()));
responseDTO.setResultado(true);
return responseDTO;
}
}
```
#### Ejemplo de request para validación :
* Perfil utilizado : jduarte
> Validación de nombre único : creación
```coffeescript
curl --location --request POST 'http://localhost:8080/bien-estados' \
--header 'Content-Type: application/json' \
--data-raw '{
"nombre":"APROBADA",
"descripcion":"TEST"
}'
```
> Validación de nombre único : actualización
```coffeescript
curl --location --request PUT 'http://localhost:8080/bien-estados' \
--header 'Content-Type: application/json' \
--data-raw '{
"id": 61,
"nombre": "NEW RECORD",
"descripcion":"NEW RECORD IV"
}'
```
> Validación de eliminación
```coffeescript
curl --location --request DELETE 'http://localhost:8080/bien-estados/61'
```
>