Reputation: 957
I am currently receiving a Map<String, String>
in the API request body. I have an entity,
class TagEntity {
@Column(name = "name", length = 128, nullable = false)
private String name;
@Column(name = "value", length = 256, nullable = false)
private String value;
}
There is a MachineEntity
which has a Set<TagEntity>
. It's a @OneToMany
mapping. I am trying to fetch the machine entities according to the name
and value
passed in the HashMap
(in request) which correspond to the TagEntity
.
I tried this code below. However, it works only if I pass one name-value pair in the request. Suppose, if the HashMap
contains 2 elements, the query returns an empty list when it should return 2 Machine Entities.
SetJoin<MachineEntity, TagEntity> join = root.join(MachineEntity_.tagEntities);
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), element.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), element.getValue())
));
}
Is there a way I can set the HashMap
in CriteriaBuilder
without iterating through the Map
? I am clueless what can be done to solve this problem. Will greatly appreciate some help.
Upvotes: 0
Views: 1237
Reputation: 40298
As always, writing the statement in (roughly) SQL helps:
SELECT ...
FROM Machine m
WHERE -- repeat for each map entry
EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?1 AND t.value = ?2)
AND EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?3 AND t.value = ?4)
...
If this suits your needs, it can be translated to criteria API roughly as follows (you will probably need to tweak it):
private Subquery<TagEntity> makeExistsSubquery(CriteriaQuery<MachineEntity> q, CriteriaBuilder cb, Map.Entry<String,String> e) {
Subquery<TagEntity> subquery = q.subquery(TagEntity.class);
Root<TagEntity> tagRoot = subquery.from(TagEntity.class);
subquery.where(cb.and(
cb.equal(tagRoot.get(TagEntity_.name), e.getKey()),
cb.equal(tagRoot.get(TagEntity_.value), e.getValue())
));
return subquery;
}
private void your_method() {
// assuming something like this exists:
CriteriaQuery<MachineEntity> query = criteriaBuilder.createQuery(MachineEntity.class);
// do this:
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.exists(makeExistsSubquery(query, criteriaBuilder, element)));
}
...
}
I am not sure how efficient is this query going to be. But I hope this hints to a solution.
Additionally I am assuming you want machines that match ALL the given tags. If you want machines matching ANY of the given tags, the SQL query would be simpler:
SELECT ...
FROM Machine m JOIN Tag t ON t.machine_id = m.id
WHERE -- repeat for each map entry
t.name = ?1 AND t.value = ?2
OR t.name = ?3 AND t.value = ?4
...
Predicate tagsPredicate = criteriaBuilder.or(
request.getTags().entrySet().stream()
.map(e -> criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), e.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), e.getValue())
))
.collect(Collectors.toList())
.toArray(new Predicate[0])
);
// add the tagsPredicate to your where clause, if the user has actually defined tag criteria
Upvotes: 1