Issue
I'm new to QueryService generated with Jhispter, and I'm trying to use them to filter a list of an entity.
It works fine when I filter on fields of this entity, but now I have to do something a bit more complicated.
I have this data set :
- Child <----- the entity I was talking about above
- Parent <----- which has a list of Child
- GrandParent <----- which has a list of Parent
What I have to do is :
- Filter Childs by Parent.name
- Filter Childs by GrandParent.name
So what I've done so far is :
private Specification<Child> createSpecification(ChildCriteria criteria) {
Specification<Child> specification = Specification.where(null);
if (criteria != null) {
if (criteria.getParentName() != null) {
speficiation = specification.and(buildReferringEntitySpecification(criteria.getParentName(), Child_.parent, Parent_.name));
}
if (criteria.getGrandParentName() != null) {
specification.and(buildJoinSpecification(criteria.getGrandParentName(), Child_.parent, Parent_.grandParent, GrandParent_.name));
}
}
}
An extract of Child, Parent, GrandParent and ChildCriteria :
@Entity
@Table(name = "Child")
public class Child extends EntityObject {
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnoreProperties("childs")
@QueryInit("GrandParent")
private Parent parent;
}
@Entity
@Table(name = "Parent")
public class Parent extends EntityObject {
@Column(name = "name")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnoreProperties("parents")
private GrandParent grandParent;
@OneToMany(mappedBy = "Parent", fetch = FetchType.LAZY)
private Set<Child> childs = new HashSet<>();
}
@Entity
@Table(name = "GrandParent")
public class GrandParent extends EntityObject {
@Column(name = "name")
private String name;
@OneToMany(mappedBy = "GrandParent", fetch = FetchType.LAZY)
private Set<Parent> parrents = new HashSet<>();
}
public class ChildCriteria implements Serializable {
private StringFilter parentName;
private StringFilter grandParentName;
public StringFilter getParentName() {
return parentName;
}
public void setParentName(StringFilter parentName) {
this.parentName= parentName;
}
public StringFilter getGrandParentName() {
return grandParentName;
}
public void setGrandParentName(StringFilter grandParentName) {
this.grandParentName= grandParentName;
}
}
The filter on parent name works, and I'd like to do the same for the grand parent name. And for that one, I tried several tips I found on SO like this, but nothing worked.
From that link, here's what I have so far :
public class ExtendedQueryService<ENTITY> extends QueryService<ENTITY>{
protected <REFERENCE, JOIN, FILTER extends Comparable<? super FILTER>> Specification<ENTITY> buildJoinSpecification(StringFilter filter, SingularAttribute<? super ENTITY, REFERENCE> reference, SingularAttribute<REFERENCE, JOIN> joinField, SingularAttribute<JOIN, FILTER> valueField) {
Specification<ENTITY> result = Specification.where((Specification) null);
if (filter.getContains() != null) {
result = this.containsSpecification(reference, joinField, valueField, filter.getContains());
}
return result;
}
protected <REFERENCE, JOIN, FILTER> Specification<ENTITY> containsSpecification(SingularAttribute<? super ENTITY, REFERENCE> reference, SingularAttribute<REFERENCE, JOIN> joinField, SingularAttribute<JOIN, FILTER> idField, String value) {
return (root, query, builder) ->
builder.equal(root.join(reference).join(joinField).get(idField), value);
}
}
I extend this class in my ChildQueryService, where I use the buildJoinSpecification method.
And it returns an empty list if I try to filter with it.
Solution
Alright, I found something in Jhipster doc. Problème was that I use a StringFilter, when I understood that, I modified few things from Jhipster functions like this :
ExtendedQueryService :
protected Specification<ENTITY> buildSpecification(StringFilter filter, Function<Root<ENTITY>, Expression<String>> metaclassFunction) {
if (filter.getEquals() != null) {
return equalsSpecification(metaclassFunction, filter.getEquals());
} else if (filter.getIn() != null) {
return valueIn(metaclassFunction, filter.getIn());
} else if (filter.getNotIn() != null) {
return valueNotIn(metaclassFunction, filter.getNotIn());
} else if (filter.getContains() != null) {
return likeUpperSpecification(metaclassFunction, filter.getContains());
} else if (filter.getDoesNotContain() != null) {
return doesNotContainSpecification(metaclassFunction, filter.getDoesNotContain());
} else if (filter.getNotEquals() != null) {
return notEqualsSpecification(metaclassFunction, filter.getNotEquals());
} else if (filter.getSpecified() != null) {
return byFieldSpecified(metaclassFunction, filter.getSpecified());
}
return null;
}
So that I could do this in my ChildQueryService :
if (criteria.getGrandParentName() != null) {
specification = specification.and(
buildSpecification(
criteria.getGrandParentName(),
root -> root.join(Child_.parent, JoinType.INNER).join(Parent_.grandParent, JoinType.INNER).get(GrandParent_.name)
)
);
}
And it works fine now ! It seems you can go as deep as you want with the join, though I didn't test it further than 2 level depth.
For the record, this is what I found in Jhipster doc, and what I based my function on :
protected <OTHER, MISC, X> Specification<ENTITY> buildReferringEntitySpecification(
Filter<X> filter,
Function<Root<ENTITY>, SetJoin<MISC, OTHER>> functionToEntity,
Function<SetJoin<MISC, OTHER>, Expression<X>> entityToColumn
) {
if (filter.getEquals() != null) {
return equalsSpecification(functionToEntity.andThen(entityToColumn), filter.getEquals());
} else if (filter.getSpecified() != null) {
return byFieldSpecified(root -> functionToEntity.apply(root), filter.getSpecified());
}
return null;
}
which was used that way for instance :
buildReferringEntitySpecification(
criteria.getGrandParentName(),
root -> root.get(Child_.parent).join(Parent_.grandParent),
GrandParent_.name
)
Using both allows to handle StringFilter and other Filters. You might have to do specific function for some Filter class though.
Answered By - derOtterDieb
Answer Checked By - Cary Denson (JavaFixing Admin)