带有任意 AND 子句的动态 spring data jpa 存储库查询

问题描述 投票:0回答:5

我正在使用

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...
??

怎么办?

实现带有可选字段的搜索选项的最佳方法是什么?

java spring spring-data spring-data-jpa querydsl
5个回答
38
投票

您可以使用 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 存储库文档


27
投票

请注意,使用新的主要版本 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 插件

  <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
}

26
投票

从 Spring Data JPA 1.10 开始,还有另一个选项是 按示例查询。 除了

JpaRepository
之外,您的存储库还应该实现 QueryByExampleExecutor 接口,您可以在其中获得如下方法:

<S extends T> Iterable<S> findAll(Example<S> 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


2
投票

游戏有点晚了,但这里的答案过于复杂......如果您更改实体的字段怎么办?如果您想支持搜索不同的实体怎么办?

您可以使用这个库:https://github.com/turkraft/spring-filter

它可以让您运行搜索查询,例如:

/search?filter= 平均(评级) > 4.5 and 品牌名称 in(“奥迪”、“路虎”)and(年份 > 2018 公里< 50000)和颜色“白色”意外是空的

结合Spring的Pageable,你将能够使用

&page=11&size=20

进行分页

0
投票

看看这个库:https://github.com/biagioT/jpa-search-helper,内部使用Specifications。它可能适合你。 通过这个库,您的控制器将能够接收如下请求:

curl -X GET \
  'https://myexampledomain.com/persons?
  firstName=Biagio
  &lastName_startsWith=Toz
  &birthDate_gte=19910101
  &country_in=IT,FR,DE
  &address_eq=Via Roma 1,Via Milano/,1,20 West/,34th Street
  &company.name_in=Microsoft,Apple,Google
  &company.employees_between=500,5000'

或:

curl -X POST -H "Content-type: application/json" -d '{
  "filter" : {
      "operator": "and", // the first filter must contain a root operator: AND, OR or NOT
      "filters" : [
        {
          "operator": "eq",
          "key": "firstName",
          "value": "Biagio"
        },
        {
          "operator": "or",
          "filters": [
            {
              "operator": "startsWith",
              "key": "lastName",
              "value": "Toz",
              "options": {
                "ignoreCase": true
              }
            },
            {
              "operator": "endsWith",
              "key": "lastName",
              "value": "ZZI",
              "options": {
                "ignoreCase": true,
                "trim" : true
              }
            }
          ]
        },
        {
          "operator": "in",
          "key": "company.name",
          "values": ["Microsoft", "Apple", "Google"]
        },
        {
          "operator": "or",
          "filters": [
            {
              "operator": "gte",
              "key": "birthDate",
              "value": "19910101"
            },
            {
              "operator": "lte",
              "key": "birthDate",
              "value": "20010101"
            }
          ]
        },
        {
          "operator": "between",
          "key" : "company.employees",
          "values": [500, 5000],
          "options": {
            "negate": true
          }
        }
      ]
  },
  "options": {
    "pageSize": 10,
    "pageOffset": 0,
    "sortKey": "birthDate",
    "sortDesc": false
  }
  
}' 'https://myexampledomain.com/persons'
© www.soinside.com 2019 - 2024. All rights reserved.