Total row count for pagination using JPA Criteria API - java

I am implementing "Advanced Search" kind of functionality for an Entity in my system such that user can search that entity using multiple conditions(eq,ne,gt,lt,like etc) on attributes of this entity. I am using JPA's Criteria API to dynamically generate the Criteria query and then using setFirstResult() & setMaxResults() to support pagination. All was fine till this point but now I want to show total number of results on results grid but I did not see a straight forward way to get total count of Criteria query.
This is how my code looks like:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Brand> cQuery = builder.createQuery(Brand.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Brand> select = cQuery.select(from);
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
select.where(pArray);
// Added orderBy clause
TypedQuery typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
typedQuery.setMaxResults(pageSize);
List resultList = typedQuery.getResultList();
My result set could be big so I don't want to load my entities for count query, so tell me efficient way to get total count like rowCount() method on Criteria (I think its there in Hibernate's Criteria).

Thanks Vladimir!
I took your idea and used separate count query to use my existing array of predicates in it. Final implementation looks like this:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Brand> cQuery = builder.createQuery(Brand.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Brand> select = cQuery.select(from);
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
cq.select(builder.count(cq.from(Brand.class)));
// Following line if commented causes [org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.enabled' [select count(generatedAlias0) from xxx.yyy.zzz.Brand as generatedAlias0 where ( generatedAlias1.enabled=:param0 ) and ( lower(generatedAlias1.description) like :param1 )]]
em.createQuery(cq);
cq.where(pArray);
Long count = em.createQuery(cq).getSingleResult();
.
.
select.where(pArray);
.
.
// Added orderBy clause
TypedQuery typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
typedQuery.setMaxResults(pageSize);
List resultList = typedQuery.getResultList()
Though this is working fine but still I am not sure why I have to write
em.createQuery(cq);
to get it working. Any Idea?

Why don't you just use count?
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> cQuery = builder.createQuery(Long.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Long> select = cQuery.select(builder.count(from));
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
select.where(pArray);
// Added orderBy clause
TypedQuery<Long> typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
//typedQuery.setMaxResults(pageSize);
// here is the size of your query
Long result = typedQuery.getSingleResult();

If you're using Hibernate as your JPA-Provider have a look at projections, especially Projections.rowCount().
You might have to execute the query twice though, first get the count then get the results.
Note that for plain JPA you might need some other approach.

I guess both of the answers work. But none of them is optimal. The problem with ThinkFloyd's answer is that createQuery is used two times. And Vladimir Ivanov has created two instances of CriteriaQuery which I think is unnecessary.
val cb = entityManager.criteriaBuilder
val cq = cb.createQuery(ManualQuery::class.java)
val manualQuery = cq.from(ManualQuery::class.java)
val predicates = ArrayList<Predicate>()
/*
predications.....
*/
cq.select(manualQuery)
.where(*predicates.toTypedArray())
.orderBy(cb.desc(manualQuery.get<ZonedDateTime>("createdDate")))
val query = entityManager.createQuery(cq)
// total rows count
val count = query.resultList.size
val indexedQuery = query
.setFirstResult((currentPage - 1) * pageSize)
.setMaxResults(itemsPerPage)
Doing go it works. And it is done in Kotlin. You do it the same way in Java.

Related

Hibernate: Query to select values from multiple tables using CriteriaQuery

Let's say, I have a query like
Select a.valA, b.valB
from tableA a join tableB b on a.joinCol = b.joinCol
where a.someCol = 1.
I want to execute it using Hibernate (and Spring Data) in one query to the database. I know, I can write just
Query query = em.createQuery(...);
List<Object[]> resultRows = (List<Object[]>)query.getResultList();
But my question would be - is it possible to do it in a typesafe way, using CriteriaQuery for example? The difficulty is, that, as you see, I need to select values from different tables. Is there some way to do this?
Simple example where an Employee has many to many relation to several jobs that he may have :
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<TableA> root = criteria.from(TableA.class);
Path<Long> qId = root.get("id");
Path<String> qTitle = root.get("title");
Join<TableA, TableB> tableTwo = root.join("joinColmn", JoinType.INNER);
criteria.multiselect(qId, qTitle, tableTwo);
List<Tuple> tuples = session.createQuery(criteria).getResultList();
for (Tuple tuple : tuples)
{
Long id = tuple.get(qId);
String title = tuple.get(qTitle);
TableB tableB= tuple.get(tableTwo);
}
but saw that there is an alternate answer here :
JPA Criteria API - How to add JOIN clause (as general sentence as possible)

In Hibernate 5, what's the CriteriaQuery equivalent of Criteria's restriction and projection?

Before Hibernate 5 deprecated the Criteria class, you could add restrictions to a Criteria to act as a constraint, and projections to act as select statements, like so
Criteria criteria = session.createCriteria(T.class)
.add(Restrictions.or(Restrictions.eq(property, constraintValue)
.set(Projection(Projections.projectionList()
.add(Projections.property(selectValue)));
But, since you now need to use CriteriaQuery like so,
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(T.class);
Root<T> root = criteriaQuery.from(T.class);
criteriaQuery.select(root);
Query<T> query = session.createQuery(criteriaQuery);
However I have been unable to figure out how to add certain things which are required in SQL statements, mainly because searching for documentation tends to wind up on documentation about Criteria, due to the naming similarity.
So, how can I recreate a simple query, like the one below, using CriteriaQuery?
SELECT selectValue
FROM tables.T
WHERE property = constraintValue
Source.
Has multiple examples, but turns out that the simple select statement we were trying to recreate can be done like so:
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<SELECTVALUETYPE> criteriaQuery = criteriaBuilder.createQuery(SELECTVALUETYPE.class);
Root<PARENTCLASS> root = criteriaQuery.from(PARENTCLASS.class);
criteriaQuery.select(root);
criteriaQuery.where(criteriaBuilder.equal(root.get(property), constraintValue));
Query<SELECTVALUETYPE> query = session.createQuery(criteriaQuery);
Note that this is a generic answer, and won't actually run. The reason being, SELECTVALUETYPE needs to be replaced with the data type of selectValue.
For example, CriteriaQuery might become:
String selectValue -> CriteriaQuery
T selectValue -> CriteriaQuery
Therefore, a working example for the statement
Select name
From Users
Where ID = 1
Could be expressed with the following block
int ID = 1;
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<User> root = criteriaQuery.from(User.class);
criteriaQuery.select(root.get("name");
criteriaQuery.where(criteriaBuilder.equal(root.get("ID"), ID));
Query<String> query = session.createQuery(criteriaQuery);
List<String>results = query.getResultList();
for(String name : results){
System.out.println("Name: " + name);
}

Hibernate Criteria : Find property nested in a list

Im trying to build using CriteriaBuilder the following query :
SELECT * FROM job j, asset a, asset_users au
where j.JOB_ID = a.ASSET_ID and a.ASSET_ID = au.ASSET_ID and au.USER_ID = 6
Where job has an asset and asset has a list of users...
I want to return just the list of jobs which have a asset that contains a given user...
I saw some guys doing it like that :
Session session = this.sessionFactory.getCurrentSession();
Criteria criteria = session.createCriteria(Company.class);
criterion = Restrictions.eq("companyRoles.name", "ADMIN");
criteria.add(criterion);
List<Company> companyList = criteria.list();
Tried to replicate that to criteriabuilder but got no luck. All i was getting is that it couldn't find my user id inside an object list (userList). Guess my example is harder because you have to access the object like (job).asset.userList.id .
BTW, tried this as well :
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Job> cq = cb.createQuery(Job.class);
Root<Job> jobRoot = cq.from(Job.class);
Join<Job, User> assetJoin = jobRoot.join("asset.userList");
cq.where(assetJoin.get("id").in(id));
got same issue... couldn't find the path.
Any help is very much appreciated!
Thanks
I think you are missing a step. You need the next level of joining.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Job> cq = cb.createQuery(Job.class);
Root<Job> jobRoot = cq.from(Job.class);
// join the asset
Join<Job, Asset> assetJoin = jobRoot.join("asset");
// join the list of users
Join<Asset, User> assetUserJoin = assetJoin.join("userList");
cq.where(assetUserJoin.get("id").in(id));
The type of assetUsrJoin.get("id") is Path<Long> or something similar. JPA Criteria API

No results retrieved from database view using Java

I have a view named V_ENTITY in my database which is populated with a few entries. Using SQL scrapbook in Eclipse I can easily do queries which return desired results.
However, when I try to retrieve the entries from my DAO class, I get nothing. My count returns 0 as number of entries, while there are 7 of them, which I can fetch with native SQL queries via scrapbook.
I have created a separate entity for that particular view:
#Entity
#Table(name = "V_ENTITY")
public class ViewEntity {
...
}
I use JPA2 CriteriaQuery API for dynamic queries I do. Here is the example for count that is bugging me:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ViewEntity> transactionQuery = cb.createQuery(ViewEntity.class);
Root<ViewEntity> ViewEntityRoot = transactionQuery.from(ViewEntity.class);
Predicate[] predicates = new Predicate[<someSize>]
predicates[<someIndex>] = cb.equal(
root.get("foreignName").as(String.class),
"%" + foreignName + "%"
)
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
countQuery.select(cb.count(ViewEntityRoot));
countQuery.where(predicates);
TypedQuery<Long> finalQuery = entityManager.createQuery(countQuery);
return finalQuery.getSingleResult().intValue();
Can anyone help me find a flaw in my code/thinking?
Use the <property name="show_sql">true</property> property to show us the generated sql pls

Simple CriteriaQuery in JPA Criteria API that is too much for a noob

I am trying to write following SQL query using JPA Criteria API
SELECT * FROM roles WHERE roles.name IN (SELECT users.role FROM users where name="somename");
and it is a bit to much for me (I have just started learing Criteria API). I got something like this:
CriteriaBuilder criteriaBuilder = manager.getCriteriaBuilder();
CriteriaQuery<RoleEntity> criteriaQuery = criteriaBuilder.createQuery(RoleEntity.class);
Root<RoleEntity> root = criteriaQuery.from(RoleEntity.class);
Subquery<UserEntity> subquery = criteriaQuery.subquery(UserEntity.class);
Root<UserEntity> subqueryRoot = subquery.from(UserEntity.class);
subquery.where(criteriaBuilder.equal(subqueryRoot.get(UserEntity_.username), username));
subquery.select(subqueryRoot);
And I have no idea how to put it all together.
Best regards,
Bartek
Fellow JPA learner here. Here's my attempt at setting it up:
// Get the criteria builder from the entity manager
CriteriaBuilder cb = manager.getCriteriaBuilder();
// Create a new criteria instance for the main query, the generic type indicates overall query results
CriteriaQuery<RoleEntity> c = cb.createQuery(RoleEntity.class);
// Root is the first from entity in the main query
Root<RoleEntity> role = criteriaQuery.from(RoleEntity.class);
// Now setup the subquery (type here is RETURN type of subquery, should match the users.role)
Subquery<RoleEntity> sq = cb.subquery(RoleEntity.class);
// Subquery selects from users
Root<UserEntity> userSQ = sq.from(UserEntity.class);
// Subquery selects users.role path, NOT the root, which is users
sq.select(userSQ.get(UserEntity_.role))
.where(cb.equal(userSQ.get(UserEntity_.username), username)); // test for name="somename"
// Now set the select list on the criteria, and add the in condition for the non-correlated subquery
c.select(role)
.where(cb.in(role).value(sq)); // can compare entities directly, this compares primary key identities automatically
Hopefully that helps!

Categories

Resources