Spring Boot 1.3.2.RELEASE
QueryDSL 3.7.2
QueryDSL Maven Plugin 1.1.3
Hibernate 4.3.11.Final
Currently, I have a Spring Boot application that has some basic CRUD functionality using Spring Data JPA (backed by Hibernate), and auditing using Spring Data Envers. I also have the following endpoint to retrieve a list of entities from:
http://localhost:8080/test-app/list
Now, I wanted to use the new QueryDSL support that Spring offers through the @QuerydslPredicate
annotation. This works fine for most fields or sub-entities, but it doesn't appear to work for collections of sub-entities. The documentation, blog posts, etc. don't seem to cover this case - and the only information I could find is that it supports "in" for simple collections (i.e. collections of String, etc.).
So, my entity is set up something like so:
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;
}
I generate my Q classes using the com.mysema.maven:apt-maven-plugin
, which generates my QPerson
with the following field:
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);
If I try to query on this though, I get an exception:
Query:
http://localhost:8080/test-app/list?pets.age=5
Exception:
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]
Now this query look like it's trying to resolve the propertyPath Person.pets.age
. It correctly identifies Person.pets
as a ListPath
, and then tries to identify CompanyAddress.addressLine1
(which seems correct). The problem is, it tries to use the entity path to get the class, which is the ListPath
instead of the QPet
:
Field field = ReflectionUtils.findField(entityPath.getClass(), path.getSegment());
Object value = ReflectionUtils.getField(field, entityPath);
The following query works as expected:
http://localhost:8080/test-app/list?name=Bob
My expectation was that by using ?pets.age=5
, the following predicate would be built:
QPerson.person.pets.any().age.eq(5)
Is this currently possible with Spring's QuerydslPredicate support? Or should I manually build the predicates from the query parameters?
As an additional question, is the following possible with QuerydslPredicate. Say I have a firstName and lastName on pet, and I want to run a query with just name=Bob
:
http://localhost:8080/test-app/pet/list?name=Bob
I would want the query predicate to be built like this:
final BooleanBuilder petBuilder = new BooleanBuilder();
petBuilder.and(QPet.firstName.equals("Bob").or(QPet.lastName.equals("Bob")));
Is that possible? From looking at the customize method of the QuerydslBinderCustomizer
it doesn't seem like it would be, since you need to bind off a field of the Q class. I'm guessing that what I want to do is not supported.
If these aren't possible, then I'll stick with manually creating the predicate, and passing that on to the repository.
You can use QuerydslBinderCustomizer
to achieve your purpose. Heres some sample code that can help you out:
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);
});
}
}
I ran into the same error. However I noticed that using the QuerydslAnnotationProcessor plugin (instead of the JPA annotation processor) allows me to query sub collections of entities as expected. You just have to mark all of your entity classes with the @QueryEntity annotation. (The JPA annotation processor automatically generates query classes for @Entity annotated classes.)
In your 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>
I'm believe I was running into the exception you encountered because I changed from the JPA Annotation Processor to the QuerydslAnnotationProcessor, for some reason I do not recall, and neglected to mark the entity class of the list in question with the @QueryEntity annotation. However I also believe I have another Spring-Data-Rest\JPA backed API that uses the JPA Annotation Processor built in August 2017, and I believe querying sub collections of entities works as expected. I'll be able to confirm that later today, and provide the versions of the relevant dependencies if that is the case. Perhaps this issue has been fixed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With