Динамический запрос spring данных jpa-репозитория с произвольными предложениями AND
Я использую Spring data jpa repositories
, получил требование предоставить функцию поиска с разными полями. Ввод полей перед поиском является необязательным. У меня есть 5 полей: EmployeeNumber
, Name
, Married
, Profession
и DateOfBirth
.
Здесь мне нужно запрашивать только с заданными значениями пользователем, а другие поля следует игнорировать .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%';
Итак, здесь мы рассматриваем введенные значения и запросы. В этом случае данные Spring имеют ограничение, указанное в этом сообщении (Не масштабируемо, и все возможные запросы должны быть записаны).
Я использую Querydsl
, но проблема существует, поскольку поля null
следует игнорировать, и необходимо разработать почти все возможные запросы. В этом case 31 queries
.
что, если поля поиска 6,7,8...
??
Каков наилучший подход для реализации опции поиска с необязательными полями?
Ответы
Ответ 1
Обратите внимание, что могут быть внесены изменения, чтобы использовать новую основную версию QueryDSL (4.x) и querydsl-jpa
В одном из наших проектов мы использовали QueryDSL
с 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();
}
И мы могли бы использовать это в репозиториях:
@Repository
public interface DataEntityRepository
extends DaoRepository<DataEntity, Long> {
Где DaoRepository
есть
@NoRepositoryBean
public interface DaoRepository<T, K extends Serializable>
extends JpaRepository<T, K>,
QueryDslPredicateExecutor<T> {
}
В связи с этим вы можете использовать методы предикатов репозитория.
Iterable<DataEntity> results = dataEntityRepository.findAll(dataEntityPredicateCreator.createPredicate(dataEntity));
Чтобы получить QClasses
, вам нужно указать QueryDSL APT Maven plugin в вашем 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>
Зависимости
<!-- 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>
Или для 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
}
Ответ 2
Вы можете использовать Спецификации, которые Spring -data выдают вам из коробки. и иметь возможность использовать критерии API для создания запросов программным способом. Для поддержки спецификаций вы можете расширить свой интерфейс репозитория с помощью интерфейса JpaSpecificationExecutor
public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {
}
Дополнительный интерфейс (JpaSpecificationExecutor) содержит методы, которые позволяют выполнять спецификации различными способами.
Например, метод findAll возвращает все объекты, соответствующие спецификации:
List<T> findAll(Specification<T> spec);
Интерфейс спецификации выглядит следующим образом:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
CriteriaBuilder builder);
}
Хорошо, так какой типичный вариант использования? Спецификации могут быть легко использованы для создания расширяемого набора предикатов на вершине объекта, который затем может быть объединен и использован с JpaRepository без необходимости объявлять запрос (метод) для каждой необходимой комбинации. Вот пример: Пример 2.15. Спецификации для клиента
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
}
};
}
}
Вы указали некоторые критерии уровня абстракции бизнес-требований и создали исполняемые спецификации. Таким образом, клиент может использовать Спецификацию следующим образом:
List customers = customerRepository.findAll(isLongTermCustomer());
Вы также можете комбинировать пример спецификации 2.17. Комбинированные спецификации
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
Как вы можете видеть, спецификации предлагают некоторые методы клея-кода для и объединить спецификации. Таким образом, расширение уровня доступа к данным просто вопрос создания новых спецификаций и объединяя их с уже существующими.
И вы можете создать сложные спецификации, вот пример
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[] {}));
}
};
}
}
Вот Документы JPA Respositories
Ответ 3
Из Spring данных JPA 1.10 есть еще один вариант: Запрос по примеру.
Ваш репозиторий должен реализовывать помимо JpaRepository
также QueryByExampleExecutor интерфейс, где вы получаете такие методы, как:
<S extends T> Iterable<S> findAll(Example<S> example)
Затем вы создаете Example для поиска:
Employee e = new Employee();
e.setEmployeeNumber(getEmployeeNumberSomewherFrom());
e.setName(getNameSomewhereFrom());
e.setMarried(getMarriedSomewhereFrom());
e.setProfession(getProfessionSomewhereFrom());
e.setDateOfBirth(getDateOfBirthSomewhereFrom());
а затем:
employeeRepository.findAll(Example.of(e));
Если некоторые параметры равны нулю, они не будут приняты в предложение WHERE, поэтому вы получите динамические запросы.
Чтобы уточнить соответствие атрибутов String, просмотрите ExampleMatcher
An ExampleMatcher
, который делает нечувствительность к регистру like
, например:
ExampleMatcher matcher = ExampleMatcher.matching().
withMatcher("profession", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING).ignoreCase());
Примеры QBE: https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example