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)
Related
The scenario is:
Entity Student
ID
Name
List Courses
Entity Course
ID
Name
Student
Now I need the list of students who are studying course 'Programming'
How can I achieve this using Criteria Query.
try this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> rootStudent = cq.from(Student.class);
Join<Student,Course> joinCourse = rootStudent.join("courses",JoinType.INNER);
cq.where(cb.equals(joinCourse.get("Name"),"Proggraming"));
cq.select(rootStudent);
List<Student> res = entityManager.createQuery(cq).getResultList();
In this example I assume that you have the JPA entities well modeled, including bidirectional relationships. It would also be recommended if it is for an API that you use pojos instead of returning a JPA entity to the web part, for this you should use multiselect instead of select and specify each of the fields you want to obtain, but as a first approximation it would be valid.
It would also be advisable to use metamodels for JPA entities instead of accessing the properties through a string with the name (join, get methods ..)
In this scenario, it does not make sense to make a subquery, I change the query to find the students of the courses Proggraming1 or Proggraming2 through a subquery(I would still prefer to do it by filtering the joins without using subquery), it would be something like this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> rootStudent = cq.from(Student.class);
Join<Student,Course> joinCourse = rootStudent.join("courses",JoinType.INNER);
Subquery<Long> subqueryIdCourse = cq.subquery(Long.class);
Root<Course> rootCourseSq = subqueryIdCourse.from(Course.class);
subqueryIdCourse.where(
cb.or(
cb.equals(rootCourseSq.get("Name"),"Proggraming1"),
cb.equals(rootCourseSq.get("Name"),"Proggraming2")))
subqueryIdCourse.select(rootCourseSq.get("ID"))
cq.where(joinCourse.get("ID").in(subqueryIdCourse.getSelection()));
cq.select(rootStudent);
List<Student> res = entityManager.createQuery(cq).getResultList();
I have a scenario where I need to get the row with maximum date. The SQL query for this would be
SELECT c.*
FROM course c
INNER JOIN
(SELECT moduleId ,MAX(endDate) AS max_date
FROM course
WHERE moduleId = 12345
GROUP BY moduleId
) customSelect
ON customSelect.moduleId = c.moduleId AND c.endDate = customSelect.max_date
WHERE c.moduleId = 12345
I need to convert this query to JPA using CriteriaBuilder.Since this isn't a direct entity join, rather a selection join to root entity, I'm having trouble to figure out how to join the custom select part to the root entity Course with below syntax:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
And then how to root.join(JoinType.INNER) to join customSelect part?
If someone can show some pointers to the syntax of of joining the root to a custom selection, that would be great.
Try this
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = builder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
criteriaQuery.select(root).where(cb.equal(root.get(Course_.moduleId), 12345));
criteriaQuery.orderBy(cb.desc(r.get(Course_.endDate)));
TypedQuery<Course> query = em.createQuery(criteriaQuery);
query.setMaxResult(1);
Course result = query.getSingleResult();
I'd like to know how to make a query in JPA where content is not inside a "third table" responsible to make relationship manyToMany.
example:
SELECT id FROM A WHERE id NOT IN (SELECT id FROM A_B WHERE idB = #id)
In this case I have Table A, Table B, and the relationShip manytomany A_B
I tried with CriteriaQuery and subquery, but it didn't work. Actually, it returned none result.
Does somebody have any example for this case?
I got my solution!
I don't know if it is completely correct, so, if someone see an error, please let me know.
The end query, created by JPA become this:
select maingroup0_.GroupId as GroupId1_, maingroup0_.idGroupAttendanceType as idGroupA2_1_, maingroup0_.name as name1_ from TUnPbxGroup maingroup0_ where maingroup0_.GroupId not in (select maingroup1_.GroupId from TUnPbxGroup maingroup1_ inner join THolidayGroup listholida2_ on maingroup1_.GroupId=listholida2_.GroupId inner join THoliday holiday3_ on listholida2_.IdHoliday=holiday3_.IdHoliday where holiday3_.IdHoliday=2)
The code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<MainGroup> query = cb.createQuery(MainGroup.class);
Root<MainGroup> root = query.from(MainGroup.class);
query.select(root);
Subquery<Long> subquery = query.subquery(Long.class);
Root<MainGroup> subRoot = subquery.from(MainGroup.class);
subquery.select(subRoot.<Long>get("id"));
Join<Holiday, Holiday> maingroups = subRoot.join("listHolidays");
subquery.where(cb.equal(maingroups.get("id"), holidayId));
query.where(cb.not(cb.in(root.get("id")).value(subquery)));
TypedQuery<MainGroup> typedQuery = em.createQuery(query);
List<MainGroup> result = typedQuery.getResultList();
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!
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.