Ich benutze Spring data jpa repositories
, Muss eine Suchfunktion mit verschiedenen Feldern angeben. Das Eingeben von Feldern vor der Suche ist optional. In 5 Feldern werden EmployeeNumber
, Name
, Married
, Profession
und DateOfBirth
angezeigt.
Hier muss ich nur mit den angegebenen Werten nach Benutzer abfragen und andere Felder sollten ignoriert werden.
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%';
Hier betrachten wir also eingegebene Werte und Abfragen. In diesem Fall haben Spring-Daten eine Einschränkung, wie in this post ( Nicht skalierbar und alle möglichen Abfragen sollten geschrieben werden ) Ich verwende Querydsl
, aber das Problem besteht immer noch, da null
-Felder ignoriert werden sollten und fast alle möglichen Abfragen entwickelt werden müssen. In diesem case 31 queries
. Was ist, wenn Suchfelder 6,7,8...
??
Was ist der beste Ansatz, um eine Suchoption mit optionalen Feldern zu implementieren?
Bitte beachten Sie, dass möglicherweise Änderungen vorgenommen werden müssen, um die neue Hauptversion von QueryDSL (4.x) und querydsl-jpa zu verwenden
In einem unserer Projekte haben wir QueryDSL
mit 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();
}
Und wir könnten dies in den Repositories verwenden:
@Repository
public interface DataEntityRepository
extends DaoRepository<DataEntity, Long> {
Wo DaoRepository
ist
@NoRepositoryBean
public interface DaoRepository<T, K extends Serializable>
extends JpaRepository<T, K>,
QueryDslPredicateExecutor<T> {
}
Denn dann können Sie Repository-Prädikatmethoden verwenden.
Iterable<DataEntity> results = dataEntityRepository.findAll(dataEntityPredicateCreator.createPredicate(dataEntity));
Um QClasses
zu erhalten, müssen Sie das QueryDSL APT Maven-Plugin in Ihrer pom.xml angeben.
<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>
Abhängigkeiten sind
<!-- 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>
Oder für 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
}
Sie können die Spezifikationen verwenden, die Spring-data Ihnen standardmäßig zur Verfügung stellt. Sie können die Kriterien-API zum programmgesteuerten Erstellen von Abfragen verwenden. Zur Unterstützung von Spezifikationen können Sie die Repository-Schnittstelle mit der JpaSpecificationExecutor-Schnittstelle erweitern
public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {
}
Die zusätzliche Schnittstelle (JpaSpecificationExecutor) enthält Methoden, mit denen Sie Spezifikationen auf verschiedene Arten ausführen können.
Beispielsweise gibt die findAll-Methode alle Entitäten zurück, die der Spezifikation entsprechen:
List<T> findAll(Specification<T> spec);
Die Spezifikationsschnittstelle sieht wie folgt aus:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
Okay, was ist der typische Anwendungsfall? Mithilfe von Spezifikationen kann auf einfache Weise ein erweiterbarer Satz von Prädikaten auf einer Entität erstellt werden, der dann mit JpaRepository kombiniert und verwendet werden kann, ohne dass für jede erforderliche Kombination eine Abfrage (Methode) deklariert werden muss. Hier ist ein Beispiel: Beispiel 2.15. Spezifikationen für einen Kunden
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
}
};
}
}
Sie haben einige Kriterien für eine Abstraktionsebene für Geschäftsanforderungen angegeben und ausführbare Spezifikationen erstellt. So kann ein Client eine Spezifikation wie folgt verwenden:
List customers = customerRepository.findAll(isLongTermCustomer());
Sie können auch Spezifikationsbeispiel 2.17 kombinieren. Kombinierte Spezifikationen
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
Wie Sie sehen können, bietet Specifications einige Klebecodemethoden zum Verketten und Kombinieren von Specifications. Die Erweiterung Ihrer Datenzugriffsebene ist daher nur eine Frage der Erstellung neuer Spezifikationsimplementierungen und ihrer Kombination mit bereits vorhandenen.
Und Sie können komplexe Spezifikationen erstellen, hier ein Beispiel
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[] {}));
}
};
}
}
Hier ist das JPA Respositories docs
Aus Spring Data JPA 1.10 gibt es eine andere Option für diese ist Abfrage nach Beispiel . Ihr Repository sollte neben JpaRepository
auch die Schnittstelle QueryByExampleExecutor implementieren, über die Sie Methoden wie die folgenden erhalten:
<S extends T> Iterable<S> findAll(Example<S> example)
Dann erstellen Sie das Beispiel , um nach Folgendem zu suchen:
Employee e = new Employee();
e.setEmployeeNumber(getEmployeeNumberSomewherFrom());
e.setName(getNameSomewhereFrom());
e.setMarried(getMarriedSomewhereFrom());
e.setProfession(getProfessionSomewhereFrom());
e.setDateOfBirth(getDateOfBirthSomewhereFrom());
und dann:
employeeRepository.findAll(Example.of(e));
Wenn einige Parameter null sind, werden sie nicht in die WHERE-Klausel übernommen, sodass Sie dynamische Abfragen erhalten.
Um den Abgleich von String-Attributen zu verfeinern, schauen Sie sich ExampleMatcher
an
Ein ExampleMatcher
, bei dem die Groß- und Kleinschreibung nicht berücksichtigt wird like
ist zum Beispiel:
ExampleMatcher matcher = ExampleMatcher.matching().
withMatcher("profession", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());
QBE-Beispiele: https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example