Consulta dinámica de repositorys jpa de datos de spring con cláusulas AND arbitrarias

Estoy usando Spring data jpa repositories . Tengo el requisito de dar a la función de búsqueda campos diferentes. Ingresar campos antes de la búsqueda es opcional. Tengo 5 campos para decir EmployeeNumber , Name , Married , Profession y DateOfBirth .
Aquí tengo que consultar solo con los valores dados por el usuario y otros campos deben ser ignorados. Ex,

 Input : EmployeeNumber: ,Name:St,Married: ,Professsion:IT,DateOfBirth: Query : Select * from Employee e where Name like 'St%' and Profession like 'IT%'; Input : EmployeeNumber:10,Name: ,Married: ,Professsion:IT,DateOfBirth: Query : Select * from Employee e where EmployeeNumber like '10%' and Profession like 'IT%'; 

Entonces aquí estamos considerando los valores ingresados ​​y las consultas. En este caso, los datos de Spring tienen una limitación como se menciona en esta publicación ( No escalable y todas las consultas posibles deben escribirse ). Estoy usando Querydsl , pero el problema persiste ya que null campos null deben ignorarse y casi todas las consultas posibles deben ser desarrollado. En este case 31 queries . ¿Qué 6,7,8... si los campos de búsqueda son 6,7,8... ?

¿Cuál es el mejor enfoque para implementar la opción de búsqueda con campos opcionales?

Tenga en cuenta que puede haber cambios que hacer para usar la nueva versión principal de QueryDSL (4.x) y querydsl-jpa


En uno de nuestros proyectos, utilizamos QueryDSL con QueryDslPredicateExecutor .

  public Predicate createPredicate(DataEntity dataEntity) { QDataEntity qDataEntity = QDataEntity.dataEntity; BooleanBuilder booleanBuilder = new BooleanBuilder(); if (!StringUtils.isEmpty(dataEntity.getCnsiConsumerNo())) { booleanBuilder .or(qDataEntity.cnsiConsumerNo.contains(dataEntity.getCnsiConsumerNo())); } if (!StringUtils.isEmpty(dataEntity.getCnsiMeterNo())) { booleanBuilder.or(qDataEntity.cnsiMeterNo.contains(dataEntity.getCnsiMeterNo())); } return booleanBuilder.getValue(); } 

Y podríamos usar esto en los repositorys:

 @Repository public interface DataEntityRepository extends DaoRepository { 

Donde DaoRepository es

 @NoRepositoryBean public interface DaoRepository extends JpaRepository, QueryDslPredicateExecutor { } 

Porque entonces, puede usar métodos de predicado de repository.

 Iterable results = dataEntityRepository.findAll(dataEntityPredicateCreator.createPredicate(dataEntity)); 

Para obtener QClasses , debe especificar el complemento QueryDSL APT Maven en su pom.xml.

     com.mysema.maven maven-apt-plugin 1.0.4   generate-sources  process   target/generated-sources com.mysema.query.apt.jpa.JPAAnnotationProcessor     

Las dependencias son

    com.mysema.querydsl querydsl-core ${querydsl.version}   com.mysema.querydsl querydsl-apt ${querydsl.version}   com.mysema.querydsl querydsl-jpa ${querydsl.version}  

O para Gradle:

 sourceSets { generated } sourceSets.generated.java.srcDirs = ['src/main/generated'] configurations { querydslapt } dependencies { // other deps .... compile "com.mysema.querydsl:querydsl-jpa:3.6.3" compile "com.mysema.querydsl:querydsl-apt:3.6.3:jpa" } task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') { source = sourceSets.main.java classpath = configurations.compile + configurations.querydslapt options.compilerArgs = [ "-proc:only", "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor" ] destinationDir = sourceSets.generated.java.srcDirs.iterator().next() } compileJava { dependsOn generateQueryDSL source generateQueryDSL.destinationDir } compileGeneratedJava { dependsOn generateQueryDSL classpath += sourceSets.main.runtimeClasspath } 

Puede usar Especificaciones que Spring-data le brinda de inmediato. y poder usar API de criterios para generar consultas de forma programática. Para respaldar las especificaciones, puede ampliar su interfaz de repository con la interfaz JpaSpecificationExecutor.

 public interface CustomerRepository extends SimpleJpaRepository, JpaSpecificationExecutor { } 

La interfaz adicional (JpaSpecificationExecutor) contiene métodos que le permiten ejecutar especificaciones de varias formas.

Por ejemplo, el método findAll devolverá todas las entidades que coincidan con la especificación:

 List findAll(Specification spec); 

La interfaz de especificación es la siguiente:

 public interface Specification { Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder); } 

De acuerdo, entonces, ¿cuál es el caso de uso típico? Las especificaciones se pueden usar fácilmente para construir un conjunto extensible de predicados sobre una entidad que luego se puede combinar y usar con JpaRepository sin la necesidad de declarar una consulta (método) para cada combinación necesaria. Aquí hay un ejemplo: Ejemplo 2.15. Especificaciones para un cliente

 public class CustomerSpecs { public static Specification isLongTermCustomer() { return new Specification() { public Predicate toPredicate( Root root, CriteriaQuery query, CriteriaBuilder builder) { LocalDate date = new LocalDate().minusYears(2); return builder.lessThan(root.get('dateField'), date); } }; } public static Specification hasSalesOfMoreThan(MontaryAmount value) { return new Specification() { public Predicate toPredicate( Root root, CriteriaQuery query, CriteriaBuilder builder) { // build query here } }; } } 

Usted expresó algunos criterios sobre el nivel de abstracción de requisitos comerciales y las especificaciones ejecutables creadas. Entonces, un cliente puede usar una especificación de la siguiente manera:

 List customers = customerRepository.findAll(isLongTermCustomer()); 

También puede combinar el Ejemplo de especificación 2.17. Especificaciones combinadas

  MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List customers = customerRepository.findAll( where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount))); 

Como puede ver, Especificaciones ofrece algunos métodos de código de cola para encadenar y combinar Especificaciones. Por lo tanto, ampliar su capa de acceso a datos es solo una cuestión de crear nuevas implementaciones de especificaciones y combinarlas con las que ya existen.

Y puede crear especificaciones complejas, aquí hay un ejemplo

 public class WorkInProgressSpecification { public static Specification findByCriteria(final SearchCriteria searchCriteria) { return new Specification() { @Override public Predicate toPredicate( Root root, CriteriaQuery query, CriteriaBuilder cb) { List predicates = new ArrayList(); if (searchCriteria.getView() != null && !searchCriteria.getView().isEmpty()) { predicates.add(cb.equal(root.get("viewType"), searchCriteria.getView())); } if (searchCriteria.getFeature() != null && !searchCriteria.getFeature().isEmpty()) { predicates.add(cb.equal(root.get("title"), searchCriteria.getFeature())); } if (searchCriteria.getEpic() != null && !searchCriteria.getEpic().isEmpty()) { predicates.add(cb.equal(root.get("epic"), searchCriteria.getEpic())); } if (searchCriteria.getPerformingGroup() != null && !searchCriteria.getPerformingGroup().isEmpty()) { predicates.add(cb.equal(root.get("performingGroup"), searchCriteria.getPerformingGroup())); } if (searchCriteria.getPlannedStartDate() != null) { System.out.println("searchCriteria.getPlannedStartDate():" + searchCriteria.getPlannedStartDate()); predicates.add(cb.greaterThanOrEqualTo(root.get("plndStartDate"), searchCriteria.getPlannedStartDate())); } if (searchCriteria.getPlannedCompletionDate() != null) { predicates.add(cb.lessThanOrEqualTo(root.get("plndComplDate"), searchCriteria.getPlannedCompletionDate())); } if (searchCriteria.getTeam() != null && !searchCriteria.getTeam().isEmpty()) { predicates.add(cb.equal(root.get("agileTeam"), searchCriteria.getTeam())); } return cb.and(predicates.toArray(new Predicate[] {})); } }; } } 

Aquí están los documentos de Respositorios JPA

Desde Spring Data JPA 1.10 hay otra opción para esto es Query By Example . Su repository debe implementar, además de JpaRepository , la interfaz QueryByExampleExecutor , donde obtiene métodos como:

  Iterable findAll(Example example) 

Luego, crea el Ejemplo para buscar como:

 Employee e = new Employee(); e.setEmployeeNumber(getEmployeeNumberSomewherFrom()); e.setName(getNameSomewhereFrom()); e.setMarried(getMarriedSomewhereFrom()); e.setProfession(getProfessionSomewhereFrom()); e.setDateOfBirth(getDateOfBirthSomewhereFrom()); 

y entonces:

 employeeRepository.findAll(Example.of(e)); 

Si algunos parámetros son nulos, no se incluirán en la cláusula WHERE para que pueda obtener consultas dinámicas.

Para refinar la coincidencia de atributos de cadena, eche un vistazo a ExampleMatcher ‘s

Un ExampleMatcher que no hace distinción entre mayúsculas y minúsculas es, por ejemplo:

 ExampleMatcher matcher = ExampleMatcher.matching(). withMatcher("profession", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase()); 

Ejemplos de QBE: https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example