Hi
Consider the following entities (pseudocode to make is more concise)
Quote:
class A
{
@Id String id;
@ManyToOne B b; // Unidirectional association.
// Other fields ...
}
class B
{
@Id String id;
// Some more fields.
}
Suppose we want to select a B if (i) there exists an A that is associated to B and (ii) if A satisfies some other predicates (e.g. some field of A equals some value).
The way I'm currently expressing this as a JPA criteria query is as follows:
Code:
List<Predicate> ps = new ArrayList<>();
CriteriaQuery<B> q = builder.createQuery(B.class);
Root<A> rootA = q.from(A.class);
Root<B> rootB = q.from(B.class); // Second root creates cross join (cartesian product).
// Make cross join an inner join by adding a corresponding predicate on PKs.
ps.add(b.equal(rootA.get(A_.b).get(B_.id), rootB.get(B_.id)));
TypedQuery<B> tq = em.createQuery(
q.select(rootB).where(ps.toArray(new Predicate[ps.size()]))
);
List<B> bs = tq.getResultList();
As far as I have tested it, the query can only be created starting from the cross join. This is because we want to select Bs, but joining A and B must start from A since the Criteria API allows to create joins only in the direction of associations (and we do not have an association from B to A). (Another point is that we want to create additional predicates over A.) The cross join of A and B is implicit in the creation of the two roots (as clearly documented in the AbstractQuery.from(..) method). Looking at the SQL query created by Hibernate I verified that there is indeed a cross join. It should be clear that we actually
can and should use an inner join over A and B. This is achieved indirectly here by creating an "equals" predicate as shown in the code.
While I think that a database's query optimizer should be able to optimize the cross join into an inner join based on the additional predicate, I'm wondering whether this use case really has to be programmed that way or whether there exists a possibility to directly express an inner join. In fact, I came up with the following alternative that appears consistent to me but which resulted in runtime exceptions thrown by Hibernate (I'm Using Hibernate 5.2.10):
Code:
List<Predicate> ps = new ArrayList<>();
CriteriaQuery<B> q = builder.createQuery(B.class);
Root<A> rootA = q.from(A.class);
From<A,B> fromB = rootA.join(A_.b);
// add predicates ...
TypedQuery<B> tq = em.createQuery(
q.select(fromB).where(ps.toArray(new Predicate[ps.size()]))
);
List<B> bs = tq.getResultList();
The exception thrown with this code is "org.hibernate.hql.internal.ast.ErrorCounter Invalid path: 'generatedAlias5.name'".