具有任意 AND 子句的动态 spring 数据 jpa 存储库查询
Dynamic spring data jpa repository query with arbitrary AND clauses
我正在使用 Spring data jpa repositories
,需要提供具有不同字段的搜索功能。在搜索前输入字段是 optional.I 有 5 个字段,例如 EmployeeNumber
、Name
、Married
、Profession
和 DateOfBirth
。
在这里我只需要查询用户给定的值,其他字段应该是 ignored.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 数据具有限制,如 this post 中所述(不可扩展,应写入所有可能的查询)
我正在使用 Querydsl
,但问题仍然存在,因为 null
字段应该被忽略并且几乎所有可能的查询都需要开发。在这case 31 queries
。
如果搜索字段是 6,7,8...
??
怎么办?
使用可选字段实现搜索选项的最佳方法是什么?
请注意,为了使用 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
,您需要在 pom.xml 中指定 QueryDSL APT Maven plugin。
<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
}
您可以使用 Spring-data 开箱即用的规格。并能够使用条件 API 构建查询 programmatically.To 支持规范您可以使用 JpaSpecificationExecutor 接口扩展您的存储库接口
public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {
}
附加接口 (JpaSpecificationExecutor ) 包含允许您以多种方式执行规范的方法。
例如,findAll 方法将 return 所有符合规范的实体:
List<T> findAll(Specification<T> spec);
Specification界面如下:
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)));
As you can see, Specifications offers some glue-code methods to chain
and combine Specifications. Thus extending your data access layer is
just a matter of creating new Specification implementations and
combining them with ones already existing.
您还可以创建复杂的规范,这里是一个示例
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[] {}));
}
};
}
}
来自 Spring Data JPA 1.10 有另一个选项是 Query By Example。
除了 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 子句,因此您可以进行动态查询。
要优化字符串属性的匹配,请查看 ExampleMatcher
的
不区分大小写的 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
游戏有点晚了,但这里的答案太复杂了……如果您更改实体的字段怎么办?如果你想支持搜索不同的实体怎么办?
您可以只使用这个库:https://github.com/turkraft/spring-filter
它将让您 运行 搜索查询,例如:
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty
结合Spring的Pageable,您将能够使用&page=11&size=20
进行分页
我正在使用 Spring data jpa repositories
,需要提供具有不同字段的搜索功能。在搜索前输入字段是 optional.I 有 5 个字段,例如 EmployeeNumber
、Name
、Married
、Profession
和 DateOfBirth
。
在这里我只需要查询用户给定的值,其他字段应该是 ignored.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 数据具有限制,如 this post 中所述(不可扩展,应写入所有可能的查询)
我正在使用 Querydsl
,但问题仍然存在,因为 null
字段应该被忽略并且几乎所有可能的查询都需要开发。在这case 31 queries
。
如果搜索字段是 6,7,8...
??
使用可选字段实现搜索选项的最佳方法是什么?
请注意,为了使用 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
,您需要在 pom.xml 中指定 QueryDSL APT Maven plugin。
<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
}
您可以使用 Spring-data 开箱即用的规格。并能够使用条件 API 构建查询 programmatically.To 支持规范您可以使用 JpaSpecificationExecutor 接口扩展您的存储库接口
public interface CustomerRepository extends SimpleJpaRepository<T, ID>, JpaSpecificationExecutor {
}
附加接口 (JpaSpecificationExecutor ) 包含允许您以多种方式执行规范的方法。
例如,findAll 方法将 return 所有符合规范的实体:
List<T> findAll(Specification<T> spec);
Specification界面如下:
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)));
As you can see, Specifications offers some glue-code methods to chain and combine Specifications. Thus extending your data access layer is just a matter of creating new Specification implementations and combining them with ones already existing.
您还可以创建复杂的规范,这里是一个示例
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[] {}));
}
};
}
}
来自 Spring Data JPA 1.10 有另一个选项是 Query By Example。
除了 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 子句,因此您可以进行动态查询。
要优化字符串属性的匹配,请查看 ExampleMatcher
的
不区分大小写的 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
游戏有点晚了,但这里的答案太复杂了……如果您更改实体的字段怎么办?如果你想支持搜索不同的实体怎么办?
您可以只使用这个库:https://github.com/turkraft/spring-filter
它将让您 运行 搜索查询,例如:
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty
结合Spring的Pageable,您将能够使用&page=11&size=20