I am trying to create a query that uses the same join multiple times. An example would look as follows:
select * from a
join b b1 on b1.id=a.id
join c c1 on c1.id=b1.id
join b b2 on b2.id=a.id
join c c2 on c2.id=b2.id
where b1.attribute = "a" and c1.attribute = "b"
where b2.attribute = "c" and c2.attribute = "d"
I need to do this utilizing Hibernate Criteria. I was thinking that something like this should work:
Criteria criteria = createCriteria(a.class, "a"); //a would be the class representing table "a"
Criteria criteria1 = criteria.createAlias(b.class, "b1").createAlias(c.class, "c1");
Criteria criteria2 = criteria.createAlias(b.class, "b2").createAlias(c.class, "c2");
However, I am getting a QueryException: duplicate assocation path exception. I have seen this so: Hibernate Create Criteria to join the same table twice - tried 2 approach with 2 difference error, but this solution doesn't work for me I think, and it also implies it is not possible. Has anyone else had better luck? Thanks!
I think that is not possible with the legacy Hibernate Criteria API. You should rather use the JPA Criteria API, especially because the legacy Hibernate Criteria API was deprecated in Hibernate 5 and removed in Hibernate 6.
Related
This question is an extension of jOOQ: returning list with join,groupby and count in single object, but now with three objects instead of two.
Idea
select A.*, B.*, C.*
from A
left join B on B.aId = A.aId
left join C on C.bId = B.bId
Concrete example
SelectWhereStep<Record> query = using(configuration())
.select(Student.STUDENT.fields())
.select(Volunteermatch.VOLUNTEERMATCH.fields())
.select(Volunteer.VOLUNTEER.fields())
.from(Student.STUDENT)
.leftJoin(Volunteermatch.VOLUNTEERMATCH).on(Volunteermatch.VOLUNTEERMATCH.STUDENTID.eq(Student.STUDENT.STUDENTID))
.leftJoin(Volunteer.VOLUNTEER).on(Volunteer.VOLUNTEER.VOLUNTEERID.eq(Volunteermatch.VOLUNTEERMATCH.VOLUNTEERID));
How do i fetch the results from the query?
You're missing a fetch() call at the end of your query.
Assuming you have something like a "Triple" class available to you (below I use the Apache Commons class), and assuming you just want the first result:
import org.apache.commons.lang3.tuple.Triple;
Record r = query.fetchAny();
return Triple.of(r.into(VOLUNTEER), r.into(VOLUNTEERMATCH), r.into(STUDENT));
If you want multiple results, then probably something like:
query.fetchStream().map(r-> Triple.of(
r.into(VOLUNTEER), r.into(VOLUNTEERMATCH), r.into(STUDENT) )
).collect(toList());
I have been trying to get Hibernate to generate me a query with a subquery in its where clause. I've used this answer as a base to help me going, but this question mentioned only one table.
However, this is what I would need (in SQL):
SELECT [...]
FROM a
LEFT OUTER JOIN b on a.idb = b.idb
LEFT OUTER JOIN c on b.idc = c.idc
[...]
LEFT OUTER JOIN k out on j.idk = k.idk
WHERE k.date = (SELECT max(date) from k in where in.idk = out.idk) OR k.date is null
As I am not very used to using Hibernate, I'm having trouble specifying these inner joins while navigating in the inner constraints.
I was able to re-create the initial criteria as in the linked answer, but I can't seem to join the criteria and the rootCriteria.
If the entities are properly joined with #ManyToOne annotations, simply joining the criteria to the previous table will be enough to propagate the criteria to the whole query.
The following code seems to work properly to add the WHERE clause I'm looking for.
DetachedCriteria kSubquery = DetachedCriteria.forClass(TableJPE.class,"j2");
kSubQuery = kSubQuery.createAlias("k","k2");
kSubQuery.setProjection(Projections.max("k2.date"));
kSubQuery = kSubQuery.add(Restrictions.eqProperty("j.id", "j2.id"));
rootCriteria.add(Restrictions.disjunction()
.add(Subqueries.propertyEq("k.date",kSubQuery))
.add(Restrictions.isNull("k.date")));
I need the equivalent of
SELECT m.id, count(i.id)
FROM master m LEFT JOIN item i on m.id = i.master_id
GROUP BY m.id, m.size
HAVING m.size <> count(i.id);
in Hibernate Criteria. Thanks to this question, I know how to get the grouped result as a list of Object[]:
ProjectionList projList = Projections.projectionList();
projList.add(Projections.groupProperty("master"));
projList.add(Projections.count("id"));
session
.createCriteria(Item.class)
.join("master")
.setProjection(projList)
.addRestriction(???) // <- my HAVING clause
.list();
I have no clue how to add the HAVING clause. I guess, it's something like Restrictions.eqProperty, but how can I refer to the count?
Is there a way how to refer to the resulting tuple elements in the query?
Hibernate Criteria API does not support HAVING clauses. Since it is deprecated anyway in newer Hibernate versions, I suggest you move to JPA Criteria API, or use HQL/JPQL or more advanced wrappers like Querydsl JPA.
You can use a sqlRestriction how workaround, something like:
Restrictions.sqlRestriction("1=1 group by this_.id, this_.size HAVING this_.size <> count(i_.id));
Here is a example:
ct.setProjection(Projections.sqlProjection(
"cobr_.client_id as clientID"
, new String[] {"clientID" }
, new Type[] { new LongType()}));
ct.add(Restrictions.sqlRestriction("1=1 group by cobr_.vlr_total,clientID having (sum(this_.vlr_net)-cobr_.vlr_total) < -20"));
I am just wondering if it is allowed in Hibernate to use the same DetachedCriteria object within one Criteria multiple times. Imagine the following case:
DetachedCriteria dCriteria = DetachedCriteria.forClass(A.class)
.add(Restrictions.eq("id", 1))
.setProjection(Projections.property("id"));
Criteria criteria = session.createCriteria(B.class)
.add(
Restrictions.or(
Restrictions.and(
Subqueries.exists(dCriteria),
Restrictions.eq("id", 1)
),
Restrictions.and(
Subqueries.notExists(dCriteria),
Restrictions.eq("id", 2)
)
)
.setProjection(Projections.property("id"));
Is the usage of dCriteria twice within the this criteria allowed? It seems to work but i am not sure if it might lead to problems in more complex cases (maybe the DetachedCriteria saves same state information during query generation?). I already did some reasearches but i couldn't find an explicit answer.
No it isn't (always) safe to re-use DetachedCriteria. For example: get a list and a rowcount, reusing the DetachedCriteria:
DetachedCriteria dc = DetachedCriteria.forClass(A.class);
Criteria c1 = dc.getExecutableCriteria(session);
c1.setProjection(Projections.rowCount());
long count = ((Number) c1.uniqueResult()).longValue();
System.out.println(count + " result(s) found:");
Criteria c2 = dc.getExecutableCriteria(session);
System.out.println(c2.list());
This prints:
Hibernate: select count(*) as y0_ from A this_
4 result(s) found:
Hibernate: select count(*) as y0_ from A this_ <-- whoops
[4] <-- whoops again
Really simple things that don't alter the DetachedCriteria might be safe, but in general wrap the generation in some kind of factory and re-generate them each time you need them.
Officially, cloning DetachedCriteria on each call to getExecutableCriteria will never happen. See their issues, particularly HHH-635 and HHH-1046 where Brett Meyer states: "The Criteria API is considered deprecated", and the developers guide (v4.3 §12) which states:
Hibernate offers an older, legacy org.hibernate.Criteria API which should be considered deprecated. No feature development will target those APIs. Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA javax.persistence.criteria.CriteriaQuery.
EDIT: In your example you re-use the same DetachedCriteria inside the same query. The same caveats therefore apply - if you, for instance, use setProjection with one of the uses, things go wrong with the second use. For example:
DetachedCriteria dCriteria = DetachedCriteria.forClass(A.class)
.add(Restrictions.eq("id", 1))
.setProjection(Projections.property("id"));
Criteria criteria = session.createCriteria(B.class)
.add(
Restrictions.or(
Restrictions.and(
Subqueries.exists(dCriteria
.add(Restrictions.eq("text", "a1")) // <-- Note extra restriction
.setProjection(Projections.property("text"))), // <-- and projection
Restrictions.eq("idx", 1)
),
Restrictions.and(
Subqueries.notExists(dCriteria),
Restrictions.eq("idx", 2)
)
))
.setProjection(Projections.property("id"));
Object o = criteria.list();
This yields the SQL:
select this_.idx as y0_ from B this_
where (
(exists
(select this_.text as y0_ from A this_ where this_.id=? and this_.text=?) and this_.idx=?)
or (not exists
(select this_.text as y0_ from A this_ where this_.id=? and this_.text=?) and this_.idx=?))
We didn't ask for the text=? part of the not exists, but we got it due to the re-use of the DetachedCriteria †
† This leads to bad situations where, if you applied the .add(Restrictions.eq("text" ... to both uses of dCriteria, it would appear twice in both the exists and not exists in the SQL
I'm joining one table to another. The join works. I want to restrict the results to records with an "Error" message that can be in either table. When I do the following, I get no results back, yet I know there should be 2.
Criteria criteria = session.createCriteria(TableName.class);
criteria.createAlias("someList", "things");
Criterion restriction1 = Restrictions.eq("status", "Error");
Criterion restriction2 = Restrictions.eq("things.anotherStatus", "Error");
criteria.add(Restrictions.or(restriction1, restriction2));
finalList = criteria.list();
I noticed that the restrictions by themselves actually work. So, if I only do the first restriction on the original table with no alias OR if I only do the second restriction on the alias table, then I get 1 result each time.
Also, a simple join SQL query like the one below works as expected:
Select count(*)
From table1 t1
Left join table2 t2 on t1.id = t2.another_id
Where t1.status = 'ERROR' or t2.anotherStatus = 'ERROR'
How can I get this right in Hibernate?
EDIT 1: I now see that Hibernate does an Inner Join when I use the #JoinColumn annotation. How can I change it to do an Outer Join instead?
EDIT 2: Even adding #Fetch(FetchMode.JOIN) still results in an inner join! What gives? The documentation clearly says it will do an outer join. The annotation now looks like this:
#OneToMany
#JoinColumn(name="ID_FK")
#Fetch(FetchMode.JOIN)
private List<Thing> things;
Answer: use criteria.createAlias("someList", "things", JoinType.LEFT_OUTER_JOIN); instead.
Explanation: When no JoinType is specified, createAlias does an inner join by default.