Queries Dinámicos en Spring Data JPA
Tuve la necesidad de crear un Query de forma dinámica con JPA, pues tenía varios métodos definidos y podrían crecer mas, algo así:
QuestionRepository.groovy
List<Question> findAllByAuthorAndEnabledAndStatusInAndParentIsNull(UserGE author,Boolean enabled,List<String> statuses, Pageable pageable)
List<Question> findAllByCampus_CodeLikeAndSubject_subjectNumberLikeAndTypeLikeAndCareer_codeLikeAndUnityLikeAndComplexityLikeAndAuthorAndEnabledAndStatusInAndParentIsNull(String campusCode, String subjectNumber, String type, String careerCode, String unity, String complexity, UserGE author, Boolean enabled,List<String> statuses, Pageable pageable)
long countByAuthorAndEnabledAndStatusInAndParentIsNull(UserGE author,Boolean enabled,List<String> statuses)
long countByCampus_CodeLikeAndSubject_subjectNumberLikeAndTypeLikeAndCareer_codeLikeAndUnityLikeAndComplexityLikeAndAuthorAndEnabledAndStatusInAndParentIsNull(String campusCode, String subjectNumber, String type, String careerCode, String unity, String complexity,UserGE author, Boolean enabled,List<String> statuses)
List<Question> findAllByEnabledAndStatusInAndParentIsNull(Boolean enabled,List<String> statuses, Pageable pageable)
List<Question> findAllByCampus_CodeLikeAndSubject_subjectNumberLikeAndTypeLikeAndCareer_codeLikeAndUnityLikeAndComplexityLikeAndEnabledAndStatusInAndParentIsNull(String campusCode, String subjectNumber, String type, String careerCode, String unity, String complexity,Boolean enabled,List<String> statuses, Pageable pageable)
long countAllByEnabledAndStatusInAndParentIsNull(Boolean enabled,List<String> statuses)
long countAllByCampus_CodeLikeAndEnabledAndStatusInAndParentIsNull(String campusCode, Boolean enabled,List<String> statuses)
long countAllByCampus_CodeLikeAndSubject_subjectNumberLikeAndTypeLikeAndCareer_codeLikeAndUnityLikeAndComplexityLikeAndEnabledAndStatusInAndParentIsNull(String campusCode, String subjectNumber, String type, String careerCode, String unity, String complexity, Boolean enabled,List<String> statuses)
List<Question> findAllByAuthorAndStatusNotInAndParentIsNull(UserGE author,List<String> statuses)
List<Question> findAllByParentAndEnabled(Question question,Boolean enabled)
Question findByUuid(String uuid)
}
Inclusive se tienen que hacer consultas sobre relaciones de la misma clase. Es aquí en dónde la documentación de Spring ayuda diciéndonos al respecto de Specifications(https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#spec...)
Describo lo que hice de manera genérica:
Uso de la interface JpaSpecificationExecutor
Sólo implementa la interface sobre el Repository que ya tengas definido:
QuestionRepository.groovy
// ....
}
Implementa el metamodelo
Te recomiendo que explores el paquete javax.persistence.metamodel, y la documentación de Hibernate para comprender mucho mejor de que trata la generación del metamodelo; en breve, te muestro un ejemplo que denota cuándo tienes atributos simples, una relación con un objeto o con una lista de objetos, lo cuçal funciona para poder buscar a través de ellos, justo como HQL o JPQL.
Question_.groovy
import javax.persistence.metamodel.SingularAttribute
import javax.persistence.metamodel.StaticMetamodel
import javax.persistence.metamodel.MapAttribute
@StaticMetamodel(Question.class)
public abstract class Question_ {
public static volatile MapAttribute<Question, Campus, String> campusCode;
public static volatile SingularAttribute<Question, String> status;
public static volatile SingularAttribute<Question, Date> dateCreated;
public static volatile SingularAttribute<Question, Long> id;
public static volatile ListAttribute<Question, List<Question>> questions;
public static volatile SingularAttribute<Question, UserGE> author;
// ....
}
Observa que el metamodelo hace referencia a la clase original mapeada con @Entity, y que por convención se le suma un guión bajo en el nombre.
Crea el Criterion Spec
Aquí es dónde aplicaremos el _dinamismo_ de la consulta pues en base a un mapa descartaremos la estructura del query:
QuestionCriteriaSpecs.groovy
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root
class QuestionCriteriaSpecs {
static Specification<Question> byParams(Map params){
{ Root<Question> root, CriteriaQuery<?> query, CriteriaBuilder builder ->
List<Predicate> predicates = [
builder.like(root.join("campus").getAt("code"), params.campusCode),
// More builder statements ...
builder.like(root.getAt("complexity"), params.complexity),
builder.isNull(root.get("parent")),
builder.equal(root.getAt("enabled"), true)
// More builder statements ...
]
if(params.containsKey("author")){
predicates << builder.equal(root.get("author"), params.author)
}
if(params.containsKey("status")){
predicates << builder.in(root.get(Question_.status), params.status)
}
if(params.dateCreated){
predicates << builder.between(root.get(Question_.dateCreated), params.dateCreated - 1, params.dateCreated + 1)
}
builder.and(*predicates)
} as Specification<Question>
}
}
Aquí hay varias observaciones:
- Se hace la implementación de la interface Specification, pero cómo estamos usando Groovy podemos hacerlo con un Closure, sin embargo, podrías hacerlo igual con Java y alguna Lambda.
- Root usa el Metamodelo definido, query y builder son los que te ayudarán a crear tu búsqueda.
- Revisá la documentación de cada clase, es muy recomendable.
Sólo úsalo...
Podemos sustituir todas las consultas anteriores por algo cómo lo siguiente:
Sólo es cuestión de manejar un Map y agregar los atributos por los cuáles quiero buscar
En resumen podría decirse que traslade complejidad pero dando un poco más de flexibilidad y re-uso.
- neodevelop's blog
- Inicie sesión o regístrese para enviar comentarios
Comentarios recientes
hace 1 semana 2 días
hace 1 semana 2 días
hace 1 semana 2 días
hace 25 semanas 5 días
hace 27 semanas 9 horas
hace 33 semanas 5 días
hace 1 año 26 semanas
hace 2 años 38 semanas
hace 2 años 42 semanas
hace 2 años 49 semanas