Spring JPA Specification IN with Predicate - java

I'm trying to accomplish the following sql query with JPA Specification's class.
SELECT p.*
FROM Path p
WHERE p.path_id IN (
SELECT s2.path_id
FROM segment s2
INNER JOIN segment_end s3 ON s2.segment_id = s3.segment_id
WHERE s3.site_name = 'ABC' and s3.passive_flag=false)
AND p.path_id IN (
SELECT s2.path_id
FROM segment s2
INNER JOIN segment_end s3 ON s2.segment_id = s3.segment_id
WHERE s3.site_name = 'ART CENTER' and s3.passive_flag=false
);
Trying to get something to the affect of this:
List<Predicate> predicates = new ArrayList<>();
Expression<String> sfExpression = null;
List<Predicate> siteNamePredicates = new ArrayList<>();
Expression<String> site1Expression = pathRoot.join("segments", JoinType.LEFT)
.join("segmentEnds", JoinType.LEFT)
.get(pathSearch.getPathSearchField().get().toString());
siteNamePredicates.add(cb.equal(site1Expression.in(site1Expression), "ABC"));
Expression<String> site2Expression = pathRoot.join("segments", JoinType.LEFT)
.join("segmentEnds", JoinType.LEFT)
.get(pathSearch.getPathSearchField().get().toString());
siteNamePredicates.add(cb.equal(site2Expression.in(site2Expression), "ART CENTER"));
predicates.add(cb.and(siteNamePredicates.toArray(new Predicate[siteNamePredicates.size() -1])));
return cb.and(predicates.toArray(new Predicate[predicates.size() - 1]));
The reason for using specifications is that we are allowing our application to AND other search criteria so using CriteriaBuilder allows me to use predicates.
Any help would be appreciated.

Related

how to write subquery using criteria

please help me out writing criteria builder for this query
SELECT *
FROM XYZ
WHERE date_v < "2020/01" AND
id NOT IN (SELECT id FROM XYZ WHERE date_v = '2020/01')
i have looked at using subqueries in jpa criteria api but i am unable to figure it
I have tried using subquery and joins but it throwing different error after all i get to know that i need to get more clarity about query criteria usages. any help much appreciated
You have to create XyzEntity with Long id and LocalDate date_v fields.
// query
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<XyzEntity> query = cb.createQuery(XyzEntity.class);
Root<XyzEntity> root = query.from(XyzEntity.class);
LocalDate date = LocalDate.of(2020, 1, 1);
// subquery
Subquery<Long> subQuery = query.subquery(Long.class);
Root<XyzEntity> subRoot = subQuery.from(XyzEntity.class);
Predicate idSubPredicate = cb.equal(root.get("id"), subRoot.get("id"));
Predicate dateSubPredicate = cb.equal(subRoot.get("date_v"), date);
subQuery.select(subRoot.get("id")).where(idSubPredicate, dateSubPredicate);
// query predicates
Predicate datePredicate = cb.greaterThan(root.get("date_v"), date);
Predicate notExistsPredicate = cb.exists(subQuery).not();
// query result
query.select(root).where(datePredicate, notExistsPredicate);
List<XyzEntity> result = entityManager.createQuery(query).getResultList();
I have mentioned the corrections in comments for the answer but I feel providing full solution seems good and helps others:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
// subquery
Subquery<Long> subQuery = query.subquery(Long.class);
Root<Entity> subRoot = subQuery.from(Entity.class);
Predicate subPredicate = cb.equal(subRoot.get("date_v"), dateValue);
subQuery.select(subRoot.get("id")).where(subPredicate);
// query predicates
Predicate datePredicate = cb.lessThan(root.get("date_v"), dateValue);
Predicate notExistsPredicate = root.get("id").in(subQuery).not();
// query result
query.select(root).where(datePredicate, notExistsPredicate);
Query d = entityManager.createQuery(query);
List<Entity> resultList = d.getResultList()

Add where clause for joined table with JPA Criteria API

I have an one to many association between vocab and vocabml,
what need to be done with simple query (join two tables and filter based on description conditions):
select * from vocab v
left join vocabml vm on vm.vocabid = v.id and vm.locale in ('uk','en'....)
where v.description like '%syst%'
or vm.description like '%syst%'
My attempt with criteria API raises exception: Caused by: java.lang.IllegalStateException: Illegal attempt to dereference path source [null.vocabmls] of basic type
My code:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Vocab> criteriaQuery = builder.createQuery(Vocab.class);
criteriaQuery.distinct(true);
Root<Vocab> root = criteriaQuery.from(Vocab.class);
root.fetch("vocabmls", JoinType.LEFT);
List<Predicate> predicates = = new ArrayList<>();
Predicate predicate = builder.or(builder.like(root.get("description"), "%" + description + "%"),
builder.like(root.get("vocabmls").get("description"), "%" + description + "%"));
predicates.add(predicate)
if(locales != null){
List<String> ids = locales.stream().map(Locale::getId).collect(Collectors.toList());
Join<Vocab, Vocabml> vocabmlJoin = root.join("vocabmls", JoinType.LEFT);
vocabmlJoin.on(builder.and(vocabmlJoin.<Vocabml> get("localeId").in(ids)));
}
criteriaQuery.where(predicates.toArray(new Predicate[] {})).orderBy(builder.asc(root.get("description")));
return entityManager.createQuery(criteriaQuery).getResultList();
I solved it like this, need to add condition using join
Predicate predicateJoin = builder.equal(vocabmlJoin.get("description"), description);
predicates.add(predicateJoin);
It will simple add where vm.description =
Also I changed this one to be OR not AND
criteriaQuery.where(builder.or(predicates.toArray(new Predicate[] {})))

Why does JPA Criteria Query not allow to correlate a subquery?

I need to match the main query with a subquery with the ID of category. JPA Criteria Query does not allow me to set the Predicate to the Category because the query is returning types PubThread.
Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Below the entire criteria query.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<PubThread> cq = criteriaBuilder.createQuery(PubThread.class);
// 1) MainQuery
// Create the FROM
Root<PubThread> rootPubThread = cq.from(PubThread.class);
// Create the JOIN from the first select: join-chaining. You only need the return for ordering. e.g. cq.orderBy(cb.asc(categoryJoin.get(Pub_.title)));
Join<Pub, PubCategory> categoryJoin = rootPubThread.join(PubThread_.pups).join(Pub_.pubCategory);
// Create the WHERE
cq.where(criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)));
// Create the SELECT, at last
cq.select(rootPubThread).distinct(true);
// 2) Subquery
Subquery<PubThread> subquery = cq.subquery(PubThread.class);
Root<PubThread> rootPubThreadSub = subquery.from(PubThread.class);
subquery.where(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId));
Join<Pub, PubCategory> categoryJoinSub = rootPubThreadSub.join(PubThread_.pups).join(Pub_.pubCategory);
subquery.select(rootPubThreadSub);
Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
subquery.where(correlatePredicate);
cq.where(criteriaBuilder.exists(subquery));
How do you formulate this Criteria Query to match this SQL code? I think I'm pretty close.
SELECT Distinct(pt2.id), pt2.name
FROM pubthread pt2
JOIN pub_pubthread ppt2 ON pt2.id = ppt2.pubThreads_id
JOIN pub p2 ON ppt2.pups_id = p2.id
JOIN pubcategory pc2 ON p2.pubCategoryId = pc2.id
WHERE pt2.id != 1 and EXISTS (
SELECT DISTINCT(pt.id) FROM pubthread pt
JOIN pub_pubthread ppt ON pt.id = ppt.pubThreads_id
JOIN pub p ON ppt.pups_id = p.id
JOIN pubcategory pc ON p.pubCategoryId = pc.id
where pc2.id = pc.id and pt.id = 1
)
I assigned the wrong object to the predicate. If you want to match a subquery's id to the parent id, you must assign the Join, not the Root, if predicates do not belong to the root. Well, how obvious is that. :) However, glad to share. Below is the new query. There is an additional Predicate at the very bottom, which solves the issue. Also in conjunction with another boolean statement.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery mainQuery = criteriaBuilder
.createQuery(PubThread.class);
// 1) MainQuery
// Create the FROM
Root<PubThread> rootPubThread = mainQuery.from(PubThread.class);
// Create the JOIN from the first select: join-chaining. You only need the return for ordering. e.g. cq.orderBy(cb.asc(categoryJoin.get(Pub_.title)));
Join<Pub, PubCategory> categoryJoin = rootPubThread.join(PubThread_.pups).join(Pub_.pubCategory);
// Create the WHERE
mainQuery.where(criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)));
// Create the SELECT, at last
mainQuery.select(rootPubThread).distinct(true);
// 2) Subquery
Subquery<PubThread> subquery = mainQuery.subquery(PubThread.class);
Root<PubThread> rootPubThreadSub = subquery.from(PubThread.class);
//subquery.where(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId));
Join<Pub, PubCategory> categoryJoinSub = rootPubThreadSub.join(PubThread_.pups).join(Pub_.pubCategory);
subquery.select(rootPubThreadSub);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate correlatePredicate = criteriaBuilder.and(
//criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread),
criteriaBuilder.equal(categoryJoinSub.get(PubCategory_.id), categoryJoin.get(PubCategory_.id)),
criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), threadId)
);
subquery.where(correlatePredicate);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate mainPredicate = criteriaBuilder.and(
criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)),
criteriaBuilder.exists(subquery)
);
//cq.where(criteriaBuilder.exists(subquery));
mainQuery.where(mainPredicate);
TypedQuery<PubThread> typedQuery = em.createQuery(mainQuery);
List<PubThread> otherPubThreads = typedQuery.getResultList();

JPA(2.0) Criteria Simple Join

I am having problem creating criteriaQuery for the following sql.
Any help would be appreciated.Lets say I have two tables Member and Person.
I am joining on name and age and having where clause for both of the table.
I am using OpenJPA(2.0)
select *
from Member
join Person
on Member.name = Person.name
and Member.age = Person.age
where Member.name = 'someOne'
and Member.age = '24'
and Person.gender = 'F'
and Person.type = 'employee'
How about something like this:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<Member> fromMember = criteria.from(Member.class);
Root<Person> fromPerson = criteria.from(Person.class);
criteria.multiselect(fromMember, fromPerson);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.equal(fromMember.get(Member_.name), fromPerson.get(Person_.name)));
predicates.add(builder.equal(fromMember.get(Member_.age), fromPerson.get(Person_.age)));
predicates.add(builder.equal(fromMember.get(Member_.name), "someOne"));
predicates.add(builder.equal(fromMember.get(Member_.age), 24));
predicates.add(builder.equal(fromPerson.get(Person_.gender), "F"));
predicates.add(builder.equal(fromPerson.get(Person_.type), "employee"));
criteria.where(predicates.toArray(new Predicate[predicates.size()]));
List<Tuple> result = em.createQuery(criteria).getResultList();
This will return a 2-element tuple made up of Member and Person. You can enumerate the individual fields in the call to multiselect if you would rather have individual fields in each tuple.

JPA 2 Criteria API using metamodel case sensitive condition

I have the following line of code to get the results based on like statement using Hibernate 4 API
Predicate predicate = cb.like(emp.get(EmployeeDetail_.empName),
empName+"%");
The generated sql statement is
select employeede0_.EMPLOYEE_NAME as EMPLOYEE1_0_ from EMPLOYEES employeede0_
where employeede0_.EMPLOYEE_NAME like 'smith%'
How can I modify my java code to have EMPLOYEE_NAME in lower case? The generated sql output should be like the following
select employeede0_.EMPLOYEE_NAME as EMPLOYEE1_0_ from EMPLOYEES employeede0_
where lower(employeede0_.EMPLOYEE_NAME) like lower('smith%')
Complete code for getting results
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
List<Predicate> criteria = new ArrayList<Predicate>();
ParameterExpression<String> pexp = cb.parameter(String.class,
"empName");
Predicate predicate = cb.like(emp.get(Employee_.empName),
empName+"%");
criteria.add(predicate);
if (criteria.size() == 1) {
c.where(criteria.get(0));
} else if (criteria.size() > 1) {
c.where(cb.and(criteria.toArray(new Predicate[0])));
}
TypedQuery<EmployeeDetail> q = entityManager.createQuery(c);
data.setResult(q.getResultList());
Use CriteriaBuilder#lower():
Predicate predicate = cb.like(cb.lower(emp.get(EmployeeDetail_.empName)),
empName.toLowerCase() + "%");

Categories