java - sirve - Consulta dinámica de repositorios jpa de datos de primavera con cláusulas AND arbitrarias
jpql tutorial español (3)
Desde Spring Data JPA 1.10 hay otra opción para esto es Query By Example . Su repositorio debe implementar, además de JpaRepository
, la interfaz QueryByExampleExecutor , donde obtiene métodos como:
<S extends T> Iterable<S> findAll(Example<S> example)
Luego, crea el Example 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
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?
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 repositorio con la interfaz JpaSpecificationExecutor.
public interface CustomerRepository extends SimpleJpaRepository<T, ID>, 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<T> findAll(Specification<T> spec);
La interfaz de especificación es la siguiente:
public interface Specification<T> {
Predicate toPredicate(Root<T> 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<Customer> isLongTermCustomer() {
return new Specification<Customer>() {
public Predicate toPredicate(
Root<Customer> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get(''dateField''), date);
}
};
}
public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
return new Specification<Customer>() {
public Predicate toPredicate(
Root<T> 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<Customer> 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<WorkInProgress> findByCriteria(final SearchCriteria searchCriteria) {
return new Specification<WorkInProgress>() {
@Override
public Predicate toPredicate(
Root<WorkInProgress> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
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.<Date>get("plndStartDate"), searchCriteria.getPlannedStartDate()));
}
if (searchCriteria.getPlannedCompletionDate() != null) {
predicates.add(cb.lessThanOrEqualTo(root.<Date>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
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<T>
.
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 repositorios:
@Repository
public interface DataEntityRepository
extends DaoRepository<DataEntity, Long> {
Donde DaoRepository
es
@NoRepositoryBean
public interface DaoRepository<T, K extends Serializable>
extends JpaRepository<T, K>,
QueryDslPredicateExecutor<T> {
}
Porque entonces, puede usar métodos de predicado de repositorio.
Iterable<DataEntity> results = dataEntityRepository.findAll(dataEntityPredicateCreator.createPredicate(dataEntity));
Para obtener QClasses
, debe especificar el complemento QueryDSL APT Maven en su pom.xml.
<build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>maven-apt-plugin</artifactId>
<version>1.0.4</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
Las dependencias son
<!-- querydsl -->
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
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
}