JPA Critera Query count results involving join - java

I'm using hibernate and the JPA criteria API and trying to create a re-usable utility method to determine how many rows a query will return.
Currently I have this:
Long countResults(CriteriaQuery cq, String alias){
CriteriaBuilder cb = em().getCriteriaBuilder();
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
Root ent = countQuery.from(cq.getResultType());
ent.alias(alias);
countQuery.select(cb.count(ent));
Predicate restriction = cq.getRestriction();
if(restriction != null){
countQuery.where(restriction);
}
return em().createQuery(countQuery).getSingleResult();
}
Which I use like this:
CriteriaBuilder cb = em().getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(modelClass());
root.alias("ct");
cq.select(root);
TypedQuery<User> query = em().createQuery(cq);
long count = countResults(cq, "ct");
And that works fine.
However, when I use a more complicated query like
Join<UserThing, Thing> j = root.join(User_.things).join(UserThing_.thing);
cq.where(somePredicate);
My call to countResults() produces exceptions like org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'myAlias.name', <AST>:0:0: unexpected end of subtree, left-hand operand of a binary operator was null
I'm guessing this has something to do with the join, and that I need to alias that somehow, but I've not had any success so far.
Help?

I had the same problem, and I solved with:
CriteriaQuery<Long> countCriteria = cb.createQuery(Long.class);
Root<EntityA> countRoot = countCriteria.from(cq.getResultType());
Set<Join<EntityA, ?>> joins = originalEntityRoot.getJoins();
for (Join<EntityA, ?> join : joins) {
countRoot.join(join.getAttribute().getName());
}
countCriteria.select(cb.count(countRoot));
if(finalPredicate != null)
countCriteria.where(finalPredicate);
TypedQuery<Long> queryCount = entityManager.createQuery(countCriteria);
Long count = queryCount.getSingleResult();
Where
originalEntityRoot is the main root where I did the query with the where clauses.

Related

Not able to set from clause with subquery in Hibernate 6

I have the below piece of the code to get count query form the original query.
But this is the line causing the issue at compile time.
countQuery.from(sqmSubQuery);
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
SqmSubQuery sqmSubQuery = (SqmSubQuery<Tuple>) countQuery.subquery(Tuple.class);
SqmSelectStatement sqmOriginalQuery = (SqmSelectStatement) query;
SqmQuerySpec sqmOriginalQuerySpec = sqmOriginalQuery.getQuerySpec();
sqmSubQuery.setQueryPart(sqmOriginalQuerySpec.copy(SqmCopyContext.simpleContext()));
Root<T> subQuerySelectRoot = (Root<T>) sqmSubQuery.getRoots().iterator().next();
sqmSubQuery.multiselect(subQuerySelectRoot.get("id").alias("id"));
countQuery.select(builder.count(builder.literal(1)));
countQuery.from(sqmSubQuery);
Based on you comment you want to select the distinct count of all employee types. The query you provided should be equivalent to SELECT COUNT(DISTINCT employee_type) FROM Employee.
This can be written in JPA as shown below:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<Employee> employeeRoot = countQuery.from(Employee.class);
countQuery.select(builder.countDistinct(employeeRoot.get("type")));
Long count = entityManager.createQuery(countQuery).getSingleResult();
where type is the name of the property that maps to the column employee_type
The type org.hibernate.query.criteria.JpaSelectCriteria declares this method:
<X> JpaDerivedRoot<X> from(jakarta.persistence.criteria.Subquery<X> subquery);
which is the one you need to call if you're trying to use a subquery in the from clause.
And SqmSelectStatement implements JpaSelectCriteria. (It is also the object which implements jakarta.persistence.criteria.CriteriaQuery.)
So you can cast any CriteriaQuery to JpaSelectCriteria and call from():
CriteriaQuery<Thing> query = ... ;
Subquery<OtherThing> subquery = ... ;
((JpaSelectCriteria<Thing>) query).from(subquery);
or whatever (I did not test this code).

JPA create count query from existing CriteriaQuery

I have a query with some predicates, I need to count total records for paging.
Currently, what I'm doing is declare 2 roots for the query to get result list (1) and the count query (2), then with each predicate, duplicate it with different root like this
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<A> root = cq.from(A.class);
CriteriaQuery<Long> cq = cb.createQuery(A.class);
Root<A> rootCount = countQuery.from(A.class);
List<Predicate> predicates = new ArrayList<>();
List<Predicate> predicatesCount = new ArrayList<>();
Predicate p = cb.equal(root.get(A.ID), 1);
predicates.add(p);
Predicate p1 = cb.equal(rootCount.get(A.ID), 1);
predicatesCount.add(p1);
...
// execute both query to get result
So the question is:
Is it possible to create count query from query (1)? Or something to reuse the predicates with count query?
Thanks for reading!
The below example showcases setting up a criteria builder/predicate restrictions, then reusing that to do a count query as well.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<EntityStub> criteriaQuery = builder.createQuery(EntityStub.class);
Root<EntityStub> entity_ = criteriaQuery.from(EntityStub.class);
entity_.alias("entitySub"); //assign alias to entity root
criteriaQuery.where(builder.equal(entity_.get("message"), "second"));
// Generic retrieve count
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<T> entity_ = countQuery.from(criteriaQuery.getResultType());
entity_.alias("entitySub"); //use the same alias in order to match the restrictions part and the selection part
countQuery.select(builder.count(entity_));
Predicate restriction = criteriaQuery.getRestriction();
if (restriction != null) {
countQuery.where(restriction); // Copy restrictions
}
Long count = entityManager.createQuery(countQuery).getSingleResult();
See if that helps you, take note of the root alias, and when doing a Count Query, make sure the Entity class type is Long.class
https://forum.hibernate.org/viewtopic.php?p=2471522#p2471522
You could use Blaze-Persistence to generate the count query for you as it's not that easy to implement such a count query efficiently.
Blaze-Persistence is a library that works on top of JPA/Hibernate and adds support for advanced SQL constructs, rich pagination support and much more. It also has a JPA Criteria implementation which you can use as a drop-in replacement. You can then convert this query to a Blaze-Persistence Core query builder which allows to generate a count query: https://github.com/Blazebit/blaze-persistence#jpa-criteria-api-quick-start
I think this guy answered your question with its utility class like so :
Long count = JpaUtils.count(entityManager, criteriaQuery);
https://stackoverflow.com/a/9246377/5611906

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()

using a ParameterExpression versus a variable in JPA Criteria API

When using the JPA Criteria API, what is the advantage of using a ParameterExpression over a variable directly? E.g. when I wish to search for a customer by name in a String variable, I could write something like
private List<Customer> findCustomer(String name) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> criteriaQuery = cb.createQuery(Customer.class);
Root<Customer> customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(cb.equal(customer.get("name"), name));
return em.createQuery(criteriaQuery).getResultList();
}
With parameters this becomes:
private List<Customer> findCustomerWithParam(String name) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> criteriaQuery = cb.createQuery(Customer.class);
Root<Customer> customer = criteriaQuery.from(Customer.class);
ParameterExpression<String> nameParameter = cb.parameter(String.class, "name");
criteriaQuery.select(customer).where(cb.equal(customer.get("name"), nameParameter));
return em.createQuery(criteriaQuery).setParameter("name", name).getResultList();
}
For conciseness I would prefer the first way, especially when the query gets longer with optional parameters. Are there any disadvantages of using parameters like this, like SQL injection?
you can use ParameterExpression like this:
assume that you have some input filter, an example could be this:
in your query you have to check the value of a fiscal Code.
let's start:
first of all create criteriaQuery and criteriaBuilder and root
CriteriaBuilder cb = _em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<RootEntity> soggettoRoot = cq.from(RootEntity.class);
1) inizialize a predicateList(use for where clause) and a paramList(use for param)
Map<ParameterExpression,String> paramList = new HashMap();
List<Predicate> predicateList = new ArrayList<>();
2 )check if the input is null and create predicateList and param
if( input.getFilterCF() != null){
//create ParameterExpression
ParameterExpression<String> cf = cb.parameter(String.class);
//if like clause
predicateList.add(cb.like(root.<String>get("cf"), cf));
paramList.put(cf , input.getFilterCF() + "%");
//if equals clause
//predicateList.add(cb.equal(root.get("cf"), cf));
//paramList.put(cf,input.getFilterCF()());
}
3) create the where clause
cq.where(cb.and(predicateList.toArray(new Predicate[predicateList.size()])));
TypedQuery<Tuple> q = _em.createQuery(cq);
4) set param value
for(Map.Entry<ParameterExpression,String> entry : paramList.entrySet())
{
q.setParameter(entry.getKey(), entry.getValue());
}
When using a parameter, likely (dependent on JPA implementation, datastore in use, and JDBC driver) the SQL will be optimised to a JDBC parameter so if you execute the same thing with a different value of the parameter it uses the same JDBC statement.
SQL injection is always down to the developer as to whether they validate some user input that is being used as a parameter.

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