Spring @QuerydslPredicate问题

时间:2016-03-10 14:11:23

标签: java spring spring-data querydsl

使用的图书馆

Spring Boot 1.3.2.RELEASE

QueryDSL 3.7.2

QueryDSL Maven插件1.1.3

Hibernate 4.3.11.Final

问题

目前,我有一个Spring Boot应用程序,它使用Spring Data JPA(由Hibernate支持)具有一些基本的CRUD功能,并使用Spring Data Envers进行审计。我还有以下端点来检索实体列表:

  

http://localhost:8080/test-app/list

现在,我想通过@QuerydslPredicate注释使用new QueryDSL support that Spring offers。这适用于大多数字段或子实体,但它似乎不适用于子实体的集合。文档,博客文章等似乎不包括这种情况 - 我能找到的唯一信息是它支持简单集合的“in”(即字符串集合等)。

所以,我的实体设置如下:

Person.java

@Data
@Entity
@Audited
public class Person {

    @Id
    private long id;

    private String name;

    private List<Pet> pets = new ArrayList<>();

}

Pet.java

@Data
@Entity
@Audited
public class Pet {

    @Id
    private long id;

    private int age;

}

我使用com.mysema.maven:apt-maven-plugin生成我的Q类,它使用以下字段生成我的QPerson

public final ListPath<com.test.Pet, com.test.QPet> pets = this.<com.test.Pet, com.test.QPet>createList("pets", com.test.Pet.class, com.test.QPet.class, PathInits.DIRECT2);

如果我尝试查询,我会得到一个例外:

查询:

  

http://localhost:8080/test-app/list?pets.age=5

例外:

10:21:37,523 ERROR [org.springframework.boot.context.web.ErrorPageFilter] (http-/127.0.0.1:8080-1) Forwarding to error page from request [/list] due to exception [null]: java.lang.NullPointerException
    at org.springframework.util.ReflectionUtils.getField(ReflectionUtils.java:143) [spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    at org.springframework.data.querydsl.binding.QuerydslPredicateBuilder.reifyPath(QuerydslPredicateBuilder.java:185) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.querydsl.binding.QuerydslPredicateBuilder.reifyPath(QuerydslPredicateBuilder.java:188) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.querydsl.binding.QuerydslPredicateBuilder.getPath(QuerydslPredicateBuilder.java:167) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.querydsl.binding.QuerydslPredicateBuilder.invokeBinding(QuerydslPredicateBuilder.java:136) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.querydsl.binding.QuerydslPredicateBuilder.getPredicate(QuerydslPredicateBuilder.java:111) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver.resolveArgument(QuerydslPredicateArgumentResolver.java:106) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver.resolveArgument(QuerydslPredicateArgumentResolver.java:48) [spring-data-commons-1.11.2.RELEASE.jar:]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:78) [spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]

现在这个查询看起来好像正在尝试解析propertyPath Person.pets.age。它正确地将Person.pets标识为ListPath,然后尝试识别CompanyAddress.addressLine1(这似乎是正确的)。问题是,它试图使用实体路径来获取类,即ListPath而不是QPet

Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());
Object value = ReflectionUtils.getField(field, entityPath);

以下查询按预期工作:

  

http://localhost:8080/test-app/list?name=Bob

我的期望是,通过使用?pets.age=5,将构建以下谓词:

QPerson.person.pets.any().age.eq(5)

目前Spring的QuerydslPredicate支持是否可行?或者我应该从查询参数手动构建谓词吗?

附加问题

作为一个额外的问题,QuerydslPredicate有以下几种可能。假设我在pet上有firstName和lastName,我想只用name=Bob运行查询:

  

http://localhost:8080/test-app/pet/list?name=Bob

我希望查询谓词的构建方式如下:

final BooleanBuilder petBuilder = new BooleanBuilder();
petBuilder.and(QPet.firstName.equals("Bob").or(QPet.lastName.equals("Bob")));

这可能吗?通过查看QuerydslBinderCustomizer的自定义方法,它似乎不会,因为您需要绑定Q类的字段。我猜我不想支持。

如果这些不可能,那么我将坚持手动创建谓词,并将其传递给存储库。

2 个答案:

答案 0 :(得分:2)

您可以使用QuerydslBinderCustomizer来达到目的。下面是一些可以帮助你的示例代码:

public interface PersonRepository extends JpaRepository<Job, Integer>,
        QueryDslPredicateExecutor<Person>, QuerydslBinderCustomizer<QJob> {

    @Override
    public default void customize(final QuerydslBindings bindings, final QPerson person)     {
        bindings.bind(person.pets.any().name).first((path, value) -> {
            return path.eq(value);
        });
    }
}

答案 1 :(得分:1)

我遇到了同样的错误。但是我注意到使用QuerydslAnnotationProcessor插件(而不是JPA注释处理器)允许我按预期查询实体的子集合。您只需使用@QueryEntity注释标记所有实体类。 (JPA注释处理器自动为@Entity注释类生成查询类。)

在你的pom中:

          <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/annotations</outputDirectory>
                            <processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.querydsl</groupId>
                        <artifactId>querydsl-apt</artifactId>
                        <version>4.1.3</version>
                    </dependency>
                </dependencies>
            </plugin>

我相信我遇到了你遇到的异常,因为我从JPA Annotation Processor更改为QuerydslAnnotationProcessor,由于某些原因我不记得了,并且忽略了用@标记列表中的实体类。 QueryEntity注释。但是我也相信我有另一个Spring-Data-Rest \ JPA支持的API,它使用2017年8月构建的JPA Annotation Processor,我相信查询实体的子集合可以按预期工作。我将能够在今天晚些时候确认,并提供相关依赖项的版本(如果是这种情况)。也许这个问题已得到修复。