Categorías
Bases de Datos

Patrón de diseño Specification con JPA

Implementar el patrón de diseño Specification de JPA permite que el código sea más simple y mantenible, además de favorecer su lectura y comprensión por parte de otros desarrolladores.

Introducción

Los métodos de búsqueda y consulta permiten recuperar objetos de las bases de datos según los criterios deseados. Dependiendo del tamaño de la aplicación y sus casos de uso el número de consultas será más o menos grande. Cuando aparece un número de consultas grande estas se vuelven complejas de mantener y pueden llegar a generar duplicidades de negocio. Para disminuir el mantenimiento de un número grande de consultas y evitar duplicidad de lógica de negocio una solución es implementar el patrón de diseño Specification.

Al obtener el objeto se necesita comprobar si cumple una o más de las condiciones. Estas condiciones pueden llevarse a cabo tanto en un método del objeto como en la lógica de persistencia en la base de datos.

Esta inmediación tiene dos inconvenientes, el número de métodos de consulta va creciendo con importancia en las aplicaciones grandes y las consultas son conjunto fijo sin posibilidad de extensión salvo añadir nuevos métodos, las consultas no son fáciles de externalizar y reutilizar.

En estos casos implementar el patrón de diseño Specification ayuda a hacer el código más mantenible, extensible, simple y de más fácil lectura para los programadores.

Problema del uso de filtros en la consulta JPA

Para solventar los inconvenientes de las consultas en JPA se opta por crear métodos con criterios individuales,es decir, consultas con filtros simples que después se combinen entre ellos dinámicamente para obtener la consulta deseada.

Aquí es donde el patrón de diseño Specification es de utilidad. Este patrón también es aplicable a las consultas presentes en las clases repositorio de acceso a la base de datos donde seguramente es más probable repetir la misma lógica de condiciones en varias consultas modificando código en las SQLs. Con los mismos inconvenientes, condiciones repetidas en varios métodos e incremento de métodos de consulta. Esta es la razón de que Spring Data implemente el patrón Specification.

¿Qué es el patrón de diseño Specification?

El patrón de diseño Specification permite encapsular una pieza del conocimiento del dominio y volver a usarlas en diferentes partes de la aplicación. Utilizándolo se mueven estas reglas de negocio a clases llamadas Specification.

El patrón de diseño Specification se obtiene a partir de una interfaz con un método a implementar para encapsular la lógica de negocio que comprueba si la condición se cumple.

Al implementar el patrón Specification se hace uso de varios patrones:

  • El patrón de diseño Visitor, es una forma de separar el algoritmo de la estructura de un objeto para facilitar el uso de métodos.
  • El patrón de diseño Composite es un patrón de diseño estructural que te permite componer objetos en estructuras de árbol y trabajar con esas estructuras como si fueran objetos individuales. Se utiliza en las operaciones lógicas and, or y not. Estas condiciones lógicas de agrupación se componen de otras independientemente de que contengan una o varias.
  • El patrón de diseño Comamnd, permite solicitar una operación a un objeto sin conocer realmente el contenido de esta operación, ni el receptor real de la misma. Para ello se encapsula la solicitud como un objeto, con lo que además facilita la parametrización de los métodos.

El patrón de diseño Builder es un patrón de diseño creacional que nos permite construir objetos complejos paso a paso. El patrón nos permite producir distintos tipos y representaciones de un objeto empleando el mismo código de construcción.

Uso de las especificaciones de JPA

A continuación se ven varios ejemplos del uso de las especificaciones en JPA desde los más sencillo a más complejo. En primer lugar se crea el método Specification e indicamos el objeto con el cual se va hacer los filtros.

Se crea la lista de Predicate en la que incluiremos cada uno de los filtros, posteriormente creamos las variables de tipo Predicate para realizar las comprobaciones de cada uno de los campos que queramos filtrar. Creamos el método builder con la función equal para filtrar y en su interior con el método root recogemos los diferentes campos del objeto instanciado anteriormente. En este caso queremos filtrar por el “id”, “activo”  de la empresa que se le incluya por parámetros.

Finalmente añadimos a la lista de Predicate cada una de las comprobaciones que hemos realizado anteriormente y mediante las operaciones lógicas and, or y not podemos hacer cualquier comprobación.

Otro ejemplo del método Specification pero un poco más complejo sería el siguiente:

Crearemos el mismo método especificando el tipo de objeto que queremos filtrar, en este caso sería igual. En este caso crearemos la relación con otro Objeto para realizar el filtrado de los campos de varios objetos a la misma vez. Realizamos un JoinColumn con las dos tablas y el campo que los relacionan y según el campo que queramos filtrar recogemos los datos de una tabla u otra.

De nuevo creamos la lista de Predicate y cada uno de los Predicate para cada filtro. 

En primer lugar filtramos el id de la empresa por eso recogemos el campo de la variable root. 

En segundo lugar realizamos el filtrado por el id del trabajador y el campo lo recogeremos de la relación creada anteriormente.

En tercer lugar filtraremos por el nombre del trabajador que este va ligado al siguiente predicate que filtramos por apellidos del trabajador y que esto es uno de los motivos por el cual se usa este filtrado y lo veremos a continuación. En este filtrado se realiza el builder.like para referirse a un string y el upper para la conversión a Mayúsculas.

En el siguiente y último paso añadiremos a la lista de Predicate cada uno de los filtros realizados anteriormente y es el que incluiremos los dos filtros por Id de cada tabla y el último será en el que incluiremos el método el cual nos permite filtrar tanto por nombre del trabajador como por apellidos a la vez. Y es aquí donde encontramos la diferencia de usar este método de filtrado.

Referencia e información a enlaces de interés:

Si necesita más información acerca de esta utilidad para poder simplificar tu código y tener más facilidad en su lectura, puede visitar:

https://reflectoring.io/spring-data-specifications/

https://sacavix.com/2020/11/como-hacer-consultas-dinamicas-con-spring-data-jpa/?utm_content=cmp-true

Spring Data JPA Specifications