CriteraQuery in toPredicate method is not working - spring

I wanted to fetch only select column from the DB using multiple where clause below is my simple implementation. Am getting the result but am getting data for all the column instead of my requested fileName column alone.
List<ImageSiloVO> queryResult = imageSiloRepo.findAll(new Specification<ImageSiloVO>() {
#Override
public Predicate toPredicate(Root<ImageSiloVO> root, CriteriaQuery<?>query, CriteriaBuilder criteriaBuilder) {
query.select(root.get("fileName"));
List<Predicate> predicates = new ArrayList<>();
if(StringUtils.isNoneBlank(imageSiloVO.getEntryNumber())) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("entryNumber"), imageSiloVO.getEntryNumber())));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});

Related

Can I use dynamic Join in queryDSL?

I'm new in QueryDSL and having concern creating a QueryDSL with joins. I want to use one method and change dynamically query's join. One query joins A, B tables and another query joins A, B, C tables. I can't find these from querydsl reference. Can I do this?
My code is below.
#Override
public List<PostPreviewDto> findPostByCategoryName(String account, String categoryName, int cursor) {
return query.select(
Projections.constructor(PostPreviewDto.class,
post.id, post.title, post.time,
user.name, user.image, layout, like.count())
)
.from(post)
.innerJoin(user).on(post.user.eq(user).and(user.account.eq(account)))
.innerJoin(category).on(category.eq(post.category).and(category.name.eq(categoryName)))
.innerJoin(like).on(post.eq(like.post))
.innerJoin(mold).on(mold.eq(post.mold))
.innerJoin(layout).on(mold.eq(layout.mold).and(layout.main.eq(true)))
.where(greaterThanCursor(cursor))
.groupBy(post)
.orderBy(post.id.desc())
.limit(PAGE_SIZE)
.fetch();
}
#Override
public List<PostPreviewDto> findPostByUserId(Long userId, int cursor) {
return query.select(
Projections.constructor(PostPreviewDto.class,
post.id, post.title, post.time,
user.name, user.image, layout, like.count())
)
.from(post)
.innerJoin(user).on(post.user.eq(user).and(user.id.eq(userId)))
.innerJoin(like).on(post.eq(like.post))
.innerJoin(mold).on(mold.eq(post.mold))
.innerJoin(layout).on(mold.eq(layout.mold).and(layout.main.eq(true)))
.where(greaterThanCursor(cursor))
.groupBy(post)
.orderBy(post.id.desc())
.limit(PAGE_SIZE)
.fetch();
}
You should be able to do something like this:
private JPQLQuery<PostPreviewDto> getBaseQuery(int cursor) {
return query.select(Projections.constructor(PostPreviewDto.class,
post.id, post.title, post.time,
user.name, user.image, layout, like.count())
)
.from(post)
.innerJoin(user).on(post.user.eq(user))
.innerJoin(like).on(post.eq(like.post))
.innerJoin(mold).on(mold.eq(post.mold))
.innerJoin(layout).on(mold.eq(layout.mold).and(layout.main.eq(true)))
.where(greaterThanCursor(cursor))
.groupBy(post)
.orderBy(post.id.desc())
.limit(PAGE_SIZE);
}
#Override
public List<PostPreviewDto> findPostByCategoryName(String account, String categoryName, int cursor) {
return getBaseQuery(cursor)
.innerJoin(category).on(category.eq(post.category))
.where(user.account.eq(account).and(category.name.eq(categoryName)))
.fetch();
}
#Override
public List<PostPreviewDto> findPostByUserId(Long userId, int cursor) {
return getBaseQuery(cursor)
.where(user.id.eq(userId))
.fetch();
}

JPA CriteriaSepcification IN clause

I'm using SpringBoot 2.2.6 with JPA and I need to do query with IN clause as mentioned in Title. I have try with:
#Override
public Predicate toPredicate(Root<Distinta> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
.....
.....
for (DistintaCriteria criteria : list) {
switch(criteria.getOperation()) {
case TEST:
Join<Entity, JoinEntity> join = root.join("joinEntity");
predicates.add(join.<Integer>get("id").in(criteria.getValue()));
}
}
where criteria.getValue() is a Integer[] array but it doesn't work. Can you help me?
Thank you all.
UPDATE
If I try the same Query with List<String> it works! With Integer I had this error:
Unaware how to convert value [[2, 3, 4, 5] : java.util.ArrayList] to requested type [java.lang.Integer]
I have solved as follows:
Join<Entity, JoinEntity> join = root.join("joinEntity");
Predicate in = join.get("id").in((List<Integer>)criteria.getValue());
predicates.add(in);
I don't know why with List<String> I don't need to cast.
Hope helps.
For in clause we need to pass a list always.
You need to convert your Integer array to Integer list using Java-8 like
List<Integer> values = Arrays.asList(criteria.getValue())
#Override
public Predicate toPredicate(Root<Distinta> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
.....
.....
for (DistintaCriteria criteria : list) {
List<Integer> values = Arrays.asList(criteria.getValue());
switch(criteria.getOperation()) {
case TEST:
Join<Entity, JoinEntity> join = root.join("joinEntity");
predicates.add(join.<Integer>get("id").in(values));
}
}
In eclipse, we will get the warning like if we pass an array
Type Integer[] of the last argument to a method in(Object...) doesn't exactly match the vararg parameter type. Cast to Object[] to confirm the non-varargs invocation, or pass individual arguments of type Object for a varargs invocation.

Merge specifications of different types in Criteria Query Specifications

I'm having an Activity entity which is in #ManyToOne relationship with Event entity and their corresponding metamodels - Activity_ and Event_ were generated by JPA model generator.
I've created specialized classes ActivitySpecifications and EventSpecifications. Those classes contain only static methods whose return Specification. For example:
public interface EventSpecifications {
static Specification<Event> newerThan(LocalDateTime date) {
return (root, cq, cb) -> cb.gt(Event_.date, date);
}
...
}
so when I want to build query matching multiple specifications, I can execute following statement using findAll on JpaSpecificationExecutor<Event> repository.
EventSpecifications.newerThan(date).and(EventSpecifications.somethingElse())
and ActivitySpecifications example:
static Specification<Activity> forActivityStatus(int status) { ... }
How do I use EventSpecifications from ActivitySpecifications ? I mean like merge specifications of different type. I'm sorry, but I don't even know how to ask it properly, but theres simple example:
I want to select all activities with status = :status and where activity.event.date is greater than :date
static Specification<Activity> forStatusAndNewerThan(int status, LocalDateTime date) {
return forActivityStatus(status)
.and((root, cq, cb) -> root.get(Activity_.event) ....
// use EventSpecifications.newerThan(date) somehow up there
}
Is something like this possible?
The closest thing that comes to my mind is using the following:
return forActivityStatus(status)
.and((root, cq, cb) -> cb.isTrue(EventSpecifications.newerThan(date).toPredicate(???, cq, cb));
where ??? requires Root<Event>, but I can only get Path<Event> using root.get(Activity_.event).
In its basic form, specifications are designed to be composable only if they refer to the same root.
However, it shouldn't be too difficult to introduce your own interface which is easily convertible to Specification and which allows for specifications refering to arbitrary entities to be composed.
First, you add the following interface:
#FunctionalInterface
public interface PathSpecification<T> {
default Specification<T> atRoot() {
return this::toPredicate;
}
default <S> Specification<S> atPath(final SetAttribute<S, T> pathAttribute) {
// you'll need a couple more methods like this one for all flavors of attribute types in order to make it fully workable
return (root, query, cb) -> {
return toPredicate(root.join(pathAttribute), query, cb);
};
}
#Nullable
Predicate toPredicate(Path<T> path, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
You then rewrite the specifications as follows:
public class ActivitySpecifications {
public static PathSpecification<Activity> forActivityStatus(ActivityStatus status) {
return (path, query, cb) -> cb.equal(path.get(Activity_.status), cb.literal(status));
}
}
public class EventSpecifications {
public static PathSpecification<Event> newerThan(LocalDateTime date) {
return (path, cq, cb) -> cb.greaterThanOrEqualTo(path.get(Event_.createdDate), date);
}
}
Once you've done that, you should be able to compose specifications in the following manner:
activityRepository.findAll(
forActivityStatus(ActivityStatus.IN_PROGRESS).atRoot()
.and(newerThan(LocalDateTime.of(2019, Month.AUGUST, 1, 0, 0)).atPath(Activity_.events))
)
The above solution has the additional advantage in that specifying WHERE criteria is decoupled from specifying paths, so if you have multiple associations between Activity and Event, you can reuse Event specifications for all of them.
Consider the following :
ClassA {
id;
}
ClassB {
foreignId; //id of A
}
For combining Specification<ClassA> specA, Specification<ClassB> specB
specB = specB.and(combineSpecs(specA);
private static Specification<ClassB> combineSpecs(Specification<ClassA> specA) {
return (root_b,query,builder) {
Subquery<ClassA> sub = query.subquery(ClassA.class);
Root<ClassA> root_a = sub.from(ClassA.class);
Predicate p1 = specA.toPredicate(root_a,query,builder);
Predicate p2 = builder.equal(root_a.get("id"),root_b.get("foreignId"));
Predicate predicate = builder.and(p1,p2);
sub.select(root_a).where(predicate);
return builder.exists(sub);
};
}

Specification OR clause, in and isEmpty/isNull

I have workers that have competences (driving licenses and such) and then there are mechanisms that require certain competences. Sometimes the mechanisms require no competences at all.
Currently I have a Specification with an in clause that works fine, but I would like it to also send out mechanisms that require no competences to operate.
public static Specification<Mechanism> hasCompetences(String searchTerm) {
return (root, query, criteriaBuilder) -> {
query.distinct(true);
List<String> list = new ArrayList<>(Arrays.asList(searchTerm.split(",")));
return root.join("competences").get("name").in(list);
};
}
If I have 3 mechanisms with competences like
Car | B-Category |
Van | C-Category |
Bicycle |(no data here) |
After requesting mechanisms?competences=B-Category it returns Car as expected, but I would like to get the Bicycle too.
Or is there a way to get all all mechanisms that don't require competences? I tried mechanisms?competences= but that returned [].
Edit:
This is where I'm at right now:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
return cb.or(
cb.isEmpty(root.join("competences")),
root.join("competences").get("name").in(list)
);
};
}
But the isEmpty is giving me this error:
java.lang.IllegalArgumentException: unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin]
Edit2:
public static Specification<Mechanism> hasCompetences(List<String> list) {
return (root, query, cb) -> {
query.distinct(true);
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
};
}
Error:
unknown collection expression type [org.hibernate.query.criteria.internal.path.SetAttributeJoin];
You have 2 errors:
The criteria to match empty collection is cb.isEmpty(root.get("competences"))
You need to specify left join. root.join("competences", JoinType.LEFT)
Without the second amendment, you make an inner join, so you will never retrieve Mechanisms with empty competences.
Update
You proposed
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
root.join("competences").get("name").in(list),
cb.isEmpty(competences)
);
isEmpty won't work on SetAttributeJoin (the result of root.join) - look point 1. above
Try
Join<Mechanism, Set<Competence>> competences = root.join("competences", JoinType.LEFT);
return cb.or(
competences.get("name").in(list),
cb.isEmpty(root.get("competences"))
);

JPA Criteria api - Total records for concrete query within pagination

I am programming function for pagination in my repository layer. Function receive as parameters spring's pageable object and some value like this:
public Page<Foo> filterFoo(Pageable pageable, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> fooQuery = cb.createQuery(Foo.class);
Root<Foo> foo = fooQuery .from(Foo.class);
fooQuery .where(adding predicate for match value);
List<Foo> result = entityManager.createQuery(fooQuery )
.setFirstResult((pageable.getPageNumber() - 1) * pageable.getPageSize())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(result, pageable, xxxx);
}
Function return spring's PageImpl object filled with my result. To PageImpl I also need set total count of objects which suit predicates. This count number have to be of course without maxResult and firstResult. Is possible create another database call with my fooQuery to get total database records for that query without limit? What is the best practise to use pageable and criteria api in JPA? Thank you in advice.
Because generated SQL uses aliases - you may need make separate query for get total count of rows.
For example:
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(countQuery.from(Foo.class)));
if (Objects.nonNull(filters)) {
countQuery.where(filters);
}
return new PageImpl<>(result, pageable, em.createQuery(countQuery).getSingleResult());
where filters is equal to your adding predicate for match value expression.
Also, you may use a TupleQuery with custom SQL function for calculate count of rows in one select query.
Like this:
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"count_over",
new SQLFunctionTemplate(
StandardBasicTypes.LONG,
"(count(?1) over())"
)
);
}
}
and Criteria:
public Page<Foo> findAll(Specification<Foo> specification, Pageable pageable) {
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Foo.class> fooRoot = cq.from(Foo.class);
cq.select(cb.tuple(fooRoot, cb.function("count_over", Long.class, fooRoot.get("id"))));
Predicate filters = specification.toPredicate(fooRoot, cq, cb);
if (Objects.nonNull(filters)) {
cq.where(filters);
}
TypedQuery<Tuple> query = em.createQuery(cq);
query.setFirstResult((int) pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
List<Tuple> result = query.getResultList();
if (result.isEmpty()) {
return new PageImpl<>(List.of());
}
return new PageImpl<>(
result.stream().map(tuple -> (Foo) tuple.get(0)).collect(toUnmodifiableList()),
pageable,
(long) result.get(0).get(1)
);
}
See more about SQLFunction: https://vladmihalcea.com/hibernate-sql-function-jpql-criteria-api-query/ and Custom SQL for Order in JPA Criteria API

Resources