I'm trying to write a query similar to
select * from Table a
where a.parent_id in
(select b.id from Table b
where b.state_cd = ?
and rownum < 100)
using the Criteria API. I can achieve the query without the rownum limitation on the subquery fine using similar code to https://stackoverflow.com/a/4668015/597419 but I cannot seem to figure out how to appose a limit on the Subquery
Below is the pseudo-code for using sub-query using Criteria API. CriteriaBuilder criteriaBuilder = entityManager. getCriteriaBuilder(); CriteriaQuery<Object> criteriaQuery = criteriaBuilder. createQuery(); Root<EMPLOYEE> from = criteriaQuery.
I first set the Book entity as the root and join it with the Author entity. Then I use the count function to determine the number of Books in the SELECT clause. And after that, I compare the id of the Author entity which got selected in the outer query with the id of the Author selected in the subquery.
CriteriaQuery. multiselect. This method takes one or more Selection items as parameters. The parameters specify the result returned by the Criteria Query. The multiselect method can be used to select a single entity, single or multiple values of the same entity or of different entities.
A subquery must be surrounded by parentheses (often by an SQL aggregate function call). Even correlated subqueries (subqueries that refer to an alias in the outer query) are allowed. Note that HQL subqueries can occur only in the select or where clauses.
In Hibernate, you can add the actual SQL restriction, but it is worth noting this will be Oracle-specific. If you switched over to PostgreSQL, this would break and you'd need LIMIT 100
instead.
DetachedCriteria criteria = DetachedCriteria.forClass(Domain.class)
.add(Restrictions.sqlRestriction("rownum < 100"));
In the JPA API, the short answer is that you can't... In your question you proposed using the Criteria API (along with a SubQuery). However it is not until you actually call EntityManager.createQuery(criteriaQuery)
that you'll get a TypedQuery
where you can specify the maxResult
value.
That said, you could break it into 2 queries, the first where you get the inner-select results (max 100) and then a 2nd Criteria
where you take the resulting list in an in()
:
// inner query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<YourClass> innerCriteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = innerCriteriaQuery.from(YourClass.class);
innerCriteriaQuery.select(yourClass).where(
cb.equal(yourClass.get(YourClass_.stateCode), someStateValue));
// list of 100 parent ids
List<YourClass> list = em.createQuery(innerCriteriaQuery).setMaxResults(100).getResultList();
// outer query
CriteriaQuery<YourClass> criteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = criteriaQuery.from(YourClass.class);
criteriaQuery.select(yourClass).where(
cb.in(yourClass.get(YourClass_.parentId)).value(list);
return em.createQuery(criteriaQuery).getResultList();
There is no JPA Criteria solution for this. You could make use of a custom SQL function that runs during SQL query generation time. All JPA providers support something like that in one way or another.
If you don't want to implement that yourself or even want a proper API for constructing such queries, I can only recommend you the library I implemented called Blaze-Persistence.
Here is the documentation showcasing the limit/offset use case with subqueries: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination
Your query could look like this with the query builder API:
criteriaBuilderFactory.create(entityManager, SomeEntity.class)
.where("id").in()
.select("subEntity.id")
.from(SomeEntity.class, "subEntity")
.where("subEntity.state").eq(someValue)
.orderByAsc("subEntity.id")
.setMaxResults(100)
.end()
It essentially boils down to using the LIMIT
SQL function that is registered by Blaze-Persistence. So when you bootstrap Blaze-Persistence with your EntityManagerFactory, you should even be able to use it like this
entityManager.createQuery(
"select * from SomeEntity where id IN(LIMIT((" +
" select id " +
" from SomeEntity subEntity " +
" where subEntity.state = :someParam " +
" order by subEntity.id asc" +
"),1)) "
)
or something like
criteriaQuery.where(
cb.in(yourClass.get(YourClass_.parentId)).value(cb.function("LIMIT", subquery));
If you are using EclipseLink the calling convention of such functions looks like OPERATOR('LIMIT', ...)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With