suppose i have the following entity domain:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE")
public abstract class Entity1 {
//some attributes
}
@Entity
@DiscriminatorValue("T1")
public class Entity2 extends Entity1 {
@OneToMany(fetch=FetchType.EAGER, cascade = { CascadeType.ALL }, mappedBy="parent")
@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Entity1Detail> details = new HashSet<Entity1Detail>();
}
@Entity
public class Entity1Detail {
@ManyToOne
@JoinColumn(name="REF")
private Entity2 parent;
@Basic
private Integer quantity;
}
@Entity
@DiscriminatorValue("T2")
public class Entity3 extends Entity1 {
//some other attributes
}
when i do a JPQL query:
select e from Entity1 e left join e.details d where d.quantity > 1
it runs well (left join ;P). however when i try to construct the same query using JPA2 Criteria API:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity1.class);
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
i get NPE in "join" because the attribute "details" doesn't belong to Entity1 (which is actually true, i have to select on Entity2.class instead). the thing is that when i have to construct my dynamic query using Criteria API i don't really know anything about hierarchy, i'm just passed a Class.
i understand that Criteria API is typesafe and all that, but is there a way to work around this? with aliases maybe (as before i used Hibernate Criteria API, traversing joins with aliases):
Criteria c = session.createCriteria(Entity1.class);
c.createAlias("details", "d");
c.add(Restrictions.ge("d.quantity", 1));
You need to base your query on entity2.details
. Since the criteria API is type-safe, it catches that entity1 has no field named "details"
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity2.class); // Must use subclass as root
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
Since Entity2 extends Entity1, you can cast your results as the parent type safely. For example:
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity1
CriteriaQuery<Entity2> q = builder.createQuery(Entity2.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity2
EDIT:
I think I misunderstood the goal here. If you want all Entity1 UNLESS they are Entity2 with details.quantity <= 1, you need to do more.
You can't use a left join from Entity1Detail to Entity1, because that is not strictly type safe. Instead, you need to join Entity2 to Entity1Detail somehow. Probably the best tool to use here is a correlated subquery.
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root<Entity1> ent1 = q.from(Entity1.class);
SubQuery<Entity2> subq = q.subquery(Entity2.class);
Root<Entity2> ent2 = subq.from(Entity2.class);
Path<Integer> quantity = ent2.join("details", JoinType.LEFT).get("quantity");
Predicate lessThan = builder.lte(quantity,1);
Predicate correlatedSubqJoin = cb.equal(ent1,ent2)
subq.where(lessThan, correlatedSubqJoin);
q.select(ent1);
q.where(builder.exists(subq).not());
The criteria API does not know that you are single table inheritance, so you have to write your queries for all inheritance strategies, including a Joined inheritance strategy.
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