Student - Course : OneToMany
JPQL :
select Student from Student student, Course course where
student.name=:STUDENTNAME and (course.courseName=:COURSENAME or
course.courseDuration=courseDuration)
Let us suppose one student might have 10 courses i want to retrieve only two records having Student - Courses(2).
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
Root<Student> studentRoot = criteriaQuery.from(Student.class);
studentRoot.fetch("courses", JoinType.LEFT);
Predicate condition = criteriaBuilder.equal(studentRoot.get("studentName"), "someName");
//how to add course condition here.
criteriaQuery.where(condition);
I Hope your Student entity has relation with Course mapping with property name course in student entity.
Here is Criteria Query
Criteria criteria = getSession().createCriteria(Student.class);
criteria.createAlias("course", "course");
criteria.add(Restrictions.eq("name", STUDENTNAME));
Criterion courseNameCrit = Restrictions.eq("course.courseName", courseName);
Criterion courseDurationCrit = Restrictions.eq("course.courseDuration", courseDuration);
criteria.add(Restrictions.disjunction().add(courseNameCrit).add(courseDurationCrit));
List<Student> studentList = criteria.list();
There is no equivalent of JPA QL “JOIN FETCH” in Criteria API.
JOIN FETCH uses EAGER fetch irrespective of the fetch type specified in the model annotation.
If you are using criteria api, you can call the getter method of child in the dao,so that it gets loaded.
Related
I have a query that I want to make Criteria Query
select u.email, st.total_amount, st.company_total from users u
join (select user_id, SUM(balance) as total_amount,SUM(company_count) as company_total from subscription s
where s.is_active = 0
group by user_id) st on u.id = st.user_id
where u.is_active = 0
order by st.company_total
I have already made 1 criteria Query
CriteriaQuery<UserImpl> innerQuery = builder.createQuery(UserImpl.class);
Root<Subscription> subscriptionRoot = innerQuery.from(Subscription.class);
innerQuery.multiselect(subscriptionRoot.get("user").get("id"), builder.sum(subscriptionRoot.get("balance")),
builder.sum(subscriptionRoot.get("companyCount")));
I don't know how to make the outer query in spring hibernate JPA. Can some help.
Defining a JOIN clause is pretty simple. You first call the from method on your CriteriaQuery object to get a Root object. In the next step, you can call the join method to define your JOIN clause.
Following is example of Docs, you can manipulate it as per your example.
You can refer Hibernate docs for the join examples. following is sample code.
CriteriaQuery<String> q = cb.createQuery(String.class);
Root<Order> order = q.from(Order.class);
q.select(order.get("shippingAddress").<String>get("state"));
CriteriaQuery<Product> q2 = cb.createQuery(Product.class);
q2.select(q2.from(Order.class)
.join("items")
.<Item,Product>join("product"));
Docs to refer
Another quick example I found is listed below:
<Y> ListJoin<X, Y> join(ListAttribute<? super X, Y> list);
Quick example (assuming Employee has list of Tasks with many-to-many relation):
CriteriaQuery<Employee> query = criteriaBuilder.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
ListJoin<Employee, Task> tasks = employee.join(Employee_.tasks);
query.select(employee)
.where(criteriaBuilder.equal(tasks.get(Task_.supervisor),
employee.get(Employee_.name)));
TypedQuery<Employee> typedQuery = entityManager.createQuery(query);
List<Employee> employees = typedQuery.getResultList();
Reference: here
I have a Parent with a OneToMany associations with a Child Table.
I'm trying to write a query with CriteriaBuilder to restrict the results returned from the Child table.
I'm adding a Predicate, something like
cb.equal(parent.get("children").get("sex"), "MALE")
If the Parent has a son or SON and Daughter it's returning that parent but also returning all the children they have.
Hibernate fires off the first query with my predicates but the second query to get the children only uses the JoinColumn in the where clause it doesn't include
cb.equal(parent.get("children").get("sex"), "MALE").
Thoughts?
I am using a SetJoin
children = parent.joinSet("children", JoinType.LEFT)
CLARIFICATION:
public static Specification<Parent> findPlanBenefits(Integer parentId) {
return (parent, query, cb) -> {
Predicate predicates = cb.conjunction();
List<Expression<Boolean>> expressions = predicates.getExpressions();
//Parent Criteria
expressions.add(cb.equal(parent.get("parentId"), parentId));
//Children Criteria
SetJoin<Parent, Children> children = parent.joinSet("children", JoinType.LEFT);
Predicate sex = cb.equal(children.get("sex"), "MALE");
children.on(sex);
return predicates;
};
}
I am afraid, the JOIN ON does not work as you expect in your answer. JOIN ON only tells how to join, and NOT how relationships are loaded.
So, in order to solve your problem you will have to filter the children after they are loaded, or fetch manually all male children with a separate query.
In order to check how JOIN ON works, you could try also the corresponding JPQL query.
UPDATE
OP told that the JPQL queryselect p from parent p join fetch children c where p.parentId = :parentId and c.sex = "MALE" works.
The corresponding CriteriaQuery would look like:
CriteriaQuery<Parent> criteria = cb.createQuery((Class<Parent>) Parent.class);
Root<Parent> parent = criteria.from(Parent.class);
criteria.select((Selection<T>) parent);
SetJoin<Parent, Children> children = parent.joinSet("children", JoinType.LEFT);
Predicate sexPredicate = cb.equal(children.get("sex"), "MALE");
parent.fetch(children);
//parent.fetch("children");//try also this
criteria.where(sexPredicate);
When you create a JOIN (especially when property is collection type, not SingularAttribute, you have to use it to build the criteria, so use
cb.equal(children.get("sex"), "MALE").
instead of
cb.equal(parent.get("children").get("sex"), "MALE").
For the future referrence, this is from another post, that helped me (link):
Instead of parent.joinSet use fetch and then cast it to join:
Join<Parent, Children> join = (Join<Parent, Children>)parent.fetch(Parent_.children);
As mentioned in the post linked above it is not perfect solution but it saved me a lot of headaches.
I have two entities Customer and Order in a one-to-many relation.
For each customer I need to count the number of associated orders and sort the results by this number.
In a native postgres query it looks like this:
select cust.id, count(order.id) from customers cust
left outer join orders order
on cust.id = order.customer_id
where .... conditions ...
group by cust.id
order by count desc;
But I must do this using CriteriaBuilder because this query is part of a larger piece of code that uses CriteriaBuilder to put in additional conditions. In Hibernate I would have probably used Projections, but I can't find anything similar in JPA.
Any help in composing the query using CriteraBuilder would be much appreciated.
Thank you in advance.
Supposing that the entity Customer has a OneToMany property like this:
#OneToMany(mappedBy = "customerId")
private Collection<Order> orders;
You can use the following query:
EntityManager em; // to be built or injected
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Customer> customer = cq.from(Customer.class);
CollectionJoin<Customer, Order> orders = customer.join(Customer_.orders, JoinType.LEFT);
cq.select(cb.tuple(customer, cb.count(orders)));
cq.where(... add some predicates here ...);
cq.groupBy(customer.get(Customer_.id));
cq.orderBy(cb.desc(cb.count(orders)));
List<Tuple> result = em.createQuery(cq).getResultList();
for (Tuple t : result) {
Customer c = (Customer) t.get(0);
Long cnt = (Long) t.get(1);
System.out.println("Customer " + c.getName() + " has " + cnt + " orders");
}
The above approach uses Metamodel. If you don't like it, you can replace Customer_.orders with "orders" and Customer_.id with "id".
If the OneToMany property is of another type, replace CollectionJoin with the collection of the proper type (ListJoin, SetJoin, MapJoin).
Use this inside the specification
cq.orderBy(cb.desc(cb.count(orders)));
Also send PageRequest(1, 10, Sort.unsorted()). This is how I did it.
If you are passing the Sort value as unsorted and then override criteria query with your own logic of sorting on your joined entity
In my database I have a Test table, with columns: testName, testType
there are 2 different tests with the same type I.e "SUN", so I want only one of them for which I use Distinct in my hibernate / criteria as below, but it still giving me both the types with the same name as "sun".
Criteria crit = session.createCriteria(Test.class);
final ResultTransformer trans = new DistinctRootEntityResultTransformer();
crit.setResultTransformer(trans);
List rsList = trans.transformList(crit.list());
Any idea what could be the reason, or any other way of filtering duplicates.
Use Projections.distinct.
Criteria crit = session.createCriteria(Test.class).setProjection(
Projections.distinct(Projections.projectionList()
.add(Projections.property("type"), "type") )
.setResultTransformer(Transformers.aliasToBean(YourBean.class));
List lst = crit.list();
where YourBean.class has a property "type". The returned list will be List<YourBean>.
Try to use :
cr.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
It work perfectly for me
I finally have found out to get values of other columns:
Criteria criteria = session.createCriteria(Test.class);
ProjectionList projectionList = Projections.projectionList();
ProjectionList projectionList2 = Projections.projectionList();
projectionList2.add(Projections.distinct(projectionList.add(Projections.property("distinctColumn"), "distinctColumn")));
projectionList2.add(Projections.property("col1"), "col1");
projectionList2.add(Projections.property("col2"), "col2");
criteria.setProjection(projectionList2);
criteria.setResultTransformer(Transformers.aliasToBean(Test.class));
List list = criteria.list();
Try to use :
Criteria criteria =
session.createCriteria(Test.class).setProjection(
Projections.distinct(Projections.property("testType")));
List<Test> rsList = criteria.list();
Had the same problem and ended up solving using the Group By projection and then adding in all the columns I needed. For example
Criteria query = session.createCriteria(Class.class)
.setProjection(Projections.projectionList()
.add(Projections.groupProperty("Col1"), "Col1")
.add(Projections.groupProperty("Col2"), "Col2"))
.setResultTransformer(Transformers.aliasToBean(Class.class));
List list = query.list();
Try
setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
Criteria crit = session.createCriteria(Test.class);
List list = crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
Projections provide the result of the marked properties only. but, it creates problem for the child entities. See my post for the real problem I faced it.
Hibernate: Parent and Child relationship data structure
Try using:
criteria.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE);
It uses default hashcodes to find matches in results.
Thanks.
Can any one help me out with Criteria for following query :
SELECT * From TableA Inner Join TableB On TableA.ID=TableB.ID
I am trying with the following Criteria
Criteria criteria = session.createCriteria(TableA.class);
criteria.setFetchMode("TableB", FetchMode.JOIN);
The above criteria retrives both the table data.
Also if I need only specific columns from TableA how will the criteria Change ?
Thanks for your time.
Edit: TableA has one-to-many relationship with TableB.
Question doesn't make sense. In hibernate, the 2 Tables should actually be entities in which case they would have a relationship between them. Are you trying to randomly join 2 tables and get a result back? If so you have to use sql and use a ResultTransformer to convert the result into objects.
private ResultTransformer getResultsTransformer()
{
ResultTransformer transformer = new AliasToBeanResultTransformer(
MyResultBean.class) {
#Override
public Object transformTuple(Object[] values, String[] aliases)
{
MyResultBean row = new MyResultBean();
for (int i = 0; i < aliases.length; i++)
{
row.set(aliases[i], values[i]);
}
return (row);
}
};
return transformer;
}
Call this as follows:
Query q = session.createSQLQuery(sql);
q.setResultTransformer(getResultsTransformer());
List<MyResultBean> list = q.list();
UPDATE: If Table A has a 1-to-Many with Table B, then I find it easiest to use Alias
Criteria criteria = getSession().createCriteria(TableA.class);
criteria.createAlias("tableB","b");
criteria.add(Restrictions.eqProperty("id", "b.id");
criteria.list();
I hope this helps. Regards,
With JOIN, you need to indicate to Hibernate that you only want the "root entity", whcih is tableA. Add the following to your code:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);