I have a CriteriaQuery on my FlowStep object.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<FlowStep> cbQuery = cb.createQuery(FlowStep.class);
Root<FlowStep> flowStep = cbQuery.from(FlowStep.class);
List<Predicate> predicates = new ArrayList<>();
Then I add many predicates depending on what I want to filter.
// add all the conditions to query
cbQuery.select(flowStep).where(predicates.toArray(new Predicate[0]));
I even add sorting stuff.
cbQuery.orderBy(orders);
Query query = em.createQuery(cbQuery);
List<FlowStep> resultList = query.getResultList();
But I need to add a join like this:
select * from flowstep fs join flowinstance fi on fi.id = fs.flowinstanceid and fi.domainid = 'Test'
so I only want to return the flow steps matching the criteria AND which are in the Test domain, and domain information is in table flowinstance. How do you add join to a CriteriaQuery?
I saw something like
Join<FlowStep, UUID> flowStepFlowInstanceJoin = flowStep.join(FlowInstance_.id);
but then I need to add the condition on the domain being equal to a value.
Is it possible to use a join like the above on a JPQL criteria query?
The initial answer was to add this before the Predicate list:
Join<FlowStep, FlowInstance> flowStepFlowInstanceJoin = flowStep.join("id", JoinType.LEFT);
flowStepFlowInstanceJoin.on(cb.equal(flowStepFlowInstanceJoin.get("domainid"), domain));
FlowStep has a flowinstanceid column and field, and flowinstance has an id field. This compiles but does not work. I get an error "Cannot join to attribute of basic type". So there needs to be a one-to-many relationship between FlowStep and FlowInstance? A FlowInstance has many flow steps so maybe
@Column(name = flowinstanceid)
private UUID flowInstanceId;
in FlowStep class needs to be changed to a JoinColumn? And add a OneToMany or ManyToOne relationship to make the above JOIN possible?
The solution was suggested by CriteriaBuilder join two tables with a custom condition and JPA many-to-one relation - need to save only Id.
In class FlowStep, we need to add the FlowInstance object and a ManyToOne annotation:
@JoinColumn(name = FLOW_INSTANCE_ID, insertable = false, updatable = false)
@ManyToOne(targetEntity = FlowInstance.class, fetch = FetchType.EAGER)
@JsonIgnore
private FlowInstance flowInstance;
Note the class already had a field for the flow instance id of type UUID, but a field of class FlowInstance is necessary, it seems, for the ManyToOne relationship.
Then when building the JPA query:
Join<FlowStep, FlowInstance> flowStepFlowInstanceJoin = flowStep.join("flowInstance", JoinType.INNER);
flowStepFlowInstanceJoin.on(cb.equal(flowStepFlowInstanceJoin.get("domainId"), domain));
This goes below the
Root<FlowStep> flowStep = cbQuery.from(FlowStep.class);
line. This makes it work. It's very important that the join is of type INNER so that it only returns the steps having the wanted domain. LEFT would return steps with a non-wanted domain, and RIGHT would return as many null rows as steps having the wanted domain.