How to select only some fields using JPA criteria - java

I have a Student entity and want to select only two fields - id and age. After reading different posts I wrote the following code:
CriteriaQuery<Student> criteriaQuery = ..
var root = criteriaQuery.from(Student.class);
criteriaQuery.multiselect(root.get("id"), root.get("age"));
typedQuery = entityManager.createQuery(criteriaQuery);
List<Student> students = typedQuery.getResultList();
However, it doesn't work. How to do it using hibernate jpa provider?

If you only want two fields and use multiselect you can use a TupleQuery like in the example below:
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
var root = criteriaQuery.from(Student.class);
criteriaQuery.multiselect(root.get("id"), root.get("age"));
typedQuery = entityManager.createQuery(criteriaQuery);
List<Tuple> students = typedQuery.getResultList();
To access the values of the tuples use the get method of it.
E.g.:
Long id = (Long) students.get(0).get(0);

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

jpa criteria - expression based on db field name

I have an entity called User, which has the following relationship to an entity called Company:
#Entity
public class User {
...
#ManyToOne
#JoinColumn(name="COMPANY_ID",referencedColumnName="ID")
private Company company = null;
...
}
And on my database, I have a User table with a "COMPANY_ID" column. How can I create a JPA criteria query using this field?
Using a criteria builder, I've tried the following expressions without success:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(User.class);
Root mockEntityRoot = cq.from(User.class);
//cq.where(cb.equal(mockEntityRoot.get("company"), 2));
//cq.where(cb.equal(mockEntityRoot.get("COMPANY_ID"), 12));
cq.where(cb.equal(mockEntityRoot.get("company.id"), 8));
entityManager.createQuery(cq).getResultList();
But I got the following error: "The attribute [company.id] from the managed type User is not present."
Thanks in advance.
I think you need an explicit join.
Notice I am using Criteria's metamodel.
This is a snippet I have, it's not the same thing as yours but you can have an idea
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Site> q = cb.createQuery(Site.class);
Root<Site> e = q.from(Site.class);
Join<Site,SiteType> siteType = e.join(Site_.siteType);
Predicate predicate = cb.conjunction();
Predicate p1 = cb.equal(siteType.get(SiteType_.id), selectedSiteType.getId());
Predicate p2 = cb.equal(e.get(Site_.markedAsDeleted), false);
predicate = cb.and(p1,p2);
q.where(predicate);
q.select(e);
TypedQuery<Site> tq = entityManager.createQuery(q);
List<Site> all = tq.getResultList();
return all;

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.

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.

Categories