In this tutorial, we will continue implementing an extension for the Criteria API using the Builder pattern and JPA Static Metamodel Generator. Please check the previous parts:
In this chapter, we provide the IN (subquery) operator, for example:
List<Order> ordersToSpainWithActiveMembership = criteriaApiHelper.select(Order.class)
.in(Order_.customer,
subquery(CustomerMembership.class)
.isTrue(CustomerMembership_.isActive)
.select(CustomerMembership_.customer),
subquery(CustomerAddress.class)
.equal(CustomerAddress_.country, Country_.name, "Spain")
.select(CustomerAddress_.customer)
)
.findAll();
This query should fetch orders for the customers matching two subqueries: the customers with an active membership and the customers who live in Spain.
First of all, to store the javax.persistence.criteria.Subquery
details, we define the SubqueryPredicate
interface. It will help us to construct the javax.persistence.criteria.Subquery
the same way as QueryPredicate
, which we created earlier to construct the javax.persistence.criteria.Predicate
:
@FunctionalInterface
public interface SubqueryPredicate<P> {
Subquery<P> apply(CommonAbstractCriteria criteria, CriteriaBuilder cb);
}
In this step, we create the SubQuery
interface to be able to filter the subquery using predicates methods from the inheriting BaseQuery
interface:
public interface SubQuery<R, Q extends SubQuery<R, Q>> extends BaseQuery<R, Q> {
}
Then after we provided the necessary filters with BaseQuery
methods, in order to finish a subquery, we may need to specify the custom subquery’s path to select that will be used for the IN operator.
For example, with the following select()
method, we can select the Customer
entity from the root CustomerAddress
entity type:
subquery(CustomerAddress.class)
.equal(CustomerAddress_.country, Country_.name, "Spain")
.equal(CustomerAddress_.city, City_.name, "Madrid")
.select(CustomerAddress_.customer)
Let’s provide the necessary select()
methods with a different set of a parent-child relations chain, returning the SubqueryPredicate
instance, which we can use later to construct the javax.persistence.criteria.Subquery
:
public interface SubQuery<R, Q extends SubQuery<R, Q>> extends BaseQuery<R, Q> {
SubqueryPredicate<R> select();
<P> SubqueryPredicate<P> select(SingularAttribute<R, P> attribute);
<P1, P2> SubqueryPredicate<P2> select(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
<P1, P2, P3> SubqueryPredicate<P3> select(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3);
}
Next, let’s create the implementation of the SubQuery
interface, and implement the defined select()
methods:
public class SubQueryImpl<R> extends BaseQueryImpl<R, SubQueryImpl<R>> implements SubQuery<R, SubQueryImpl<R>> {
}
When we construct a subquery, first we specify the Root type as the entity that we use to start a query from. Therefore, the constructor should accept the Root type:
public class SubQueryImpl<R> extends BaseQueryImpl<R, SubQueryImpl<R>> implements SubQuery<R, SubQueryImpl<R>> {
private final Class<R> type;
public SubQueryImpl(Class<R> type) {
this.type = type;
}
@Override
protected SubQueryImpl<R> self() {
return this;
}
}
Next, we implement the defined select()
methods. Each method should return an instance of SubqueryPredicate
:
new SubqueryPredicate<R>() {
@Override
public Subquery<R> apply(CommonAbstractCriteria criteria, CriteriaBuilder cb) {
Subquery<R> subquery = criteria.subquery(type);
Root<R> subqueryRoot = subquery.from(type);
subquery
.select(subqueryRoot)
.where(buildPredicates(subquery, predicates, cb, subqueryRoot));
return subquery;
}
};
The complete implementation of all the defined select()
methods:
public class SubQueryImpl<R> extends BaseQueryImpl<R, SubQueryImpl<R>> implements SubQuery<R, SubQueryImpl<R>> {
...
@Override
public SubqueryPredicate<R> select() {
return (query, cb) -> {
Subquery<R> subquery = query.subquery(type);
Root<R> subqueryRoot = subquery.from(type);
subquery
.select(subqueryRoot)
.where(buildPredicates(subquery, predicates, cb, subqueryRoot));
return subquery;
};
}
@Override
public <P> SubqueryPredicate<P> select(SingularAttribute<R, P> attribute) {
return (query, cb) -> {
Subquery<P> subquery = query.subquery(attribute.getJavaType());
Root<R> subqueryRoot = subquery.from(type);
subquery
.select(subqueryRoot.get(attribute))
.where(buildPredicates(subquery, predicates, cb, subqueryRoot));
return subquery;
};
}
@Override
public <P1, P2> SubqueryPredicate<P2> select(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
return (query, cb) -> {
Subquery<P2> subquery = query.subquery(attribute2.getJavaType());
Root<R> subqueryRoot = subquery.from(type);
subquery
.select(subqueryRoot.get(attribute1).get(attribute2))
.where(buildPredicates(subquery, predicates, cb, subqueryRoot));
return subquery;
};
}
@Override
public <P1, P2, P3> SubqueryPredicate<P3> select(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3) {
return (query, cb) -> {
Subquery<P3> subquery = query.subquery(attribute3.getJavaType());
Root<R> subqueryRoot = subquery.from(type);
subquery
.select(subqueryRoot.get(attribute1).get(attribute2).get(attribute3))
.where(buildPredicates(subquery, predicates, cb, subqueryRoot));
return subquery;
};
}
}
Now, we can compose a subquery using the SubQueryImpl
and build an instance of the SubqueryPredicate
. But to apply the IN operator, we should define the in()
filter in the BaseQuery
interface.
In this section, we will define the absent IN filter in the BaseQuery
interface. This method should accept the SubqueryPredicate<R>
varargs argument as the selected value of SubQuery
:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
...
Q in(SubqueryPredicate<R>... values);
}
Next, we can specify additional in()
methods accepting SingularAttribute
parameters to reach the nested relation which should be used in the IN operator.
In the following example, we use the in()
filter to match the Address’s city
entity with the Location
subquery, returning the City
entity:
.in(Address_.city,
subquery(Location.class)
.isTrue(Location_.hasPriority)
.select(Location_.city)
)
Let’s define these additional in()
methods accepting a different set of SingularAttribute
parent-child arguments, allowing us to use nested relations:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
...
<V> Q in(SingularAttribute<R, V> attribute, SubqueryPredicate<V>... values);
<P, V> Q in(SingularAttribute<R, P> attribute1, SingularAttribute<P, V> attribute2, SubqueryPredicate<V>... values);
<P1, P2, V> Q in(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, V> attribute3, SubqueryPredicate<V>... values);
}
Next, let’s implement defined in()
methods in the BaseQueryImpl
. Each method should first map the SubqueryPredicate
varargs parameters to the javax.persistence.criteria.Subquery
instances using SubqueryPredicate#apply()
method and, after that, create a predicate with the IN operator for the path provided by SingularAttribute
parameters:
Predicate[] predicates = Arrays.stream(subqueryPredicates)
.map(t -> t.apply(CommonAbstractCriteria, CriteriaBuilder))
.map(subquery -> root.get(attribute).in(subquery))
.toArray(Predicate[]::new);
Now we can apply the CriteriaBuilder#and()
to make a conjunction of the obtained predicates and finally, construct an instance of the QueryPredicate
:
new QueryPredicate<R>() {
@Override
public Predicate apply(CommonAbstractCriteria criteria, CriteriaBuilder cb, Path<R> root) {
Predicate[] predicates = Arrays.stream(subqueryPredicates)
.map(t -> t.apply(criteria, cb))
.map(subquery -> root.get(attribute).in(subquery))
.toArray(Predicate[]::new);
return cb.and(predicates);
}
};
There is the complete implementation of all in()
methods:
public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> {
...
@SafeVarargs
@Override
public final Q in(SubqueryPredicate<R>... values) {
predicates.add((criteria, cb, root) ->
cb.and(
Arrays.stream(values)
.map(t -> t.apply(criteria, cb))
.map(root::in)
.toArray(Predicate[]::new)
)
);
return self();
}
@SafeVarargs
@Override
public final <V> Q in(SingularAttribute<R, V> attribute, SubqueryPredicate<V>... values) {
predicates.add((criteria, cb, root) ->
cb.and(
Arrays.stream(values)
.map(t -> t.apply(criteria, cb))
.map(subquery -> root.get(attribute).in(subquery))
.toArray(Predicate[]::new)
)
);
return self();
}
@SafeVarargs
@Override
public final <P, V> Q in(SingularAttribute<R, P> attribute1, SingularAttribute<P, V> attribute2, SubqueryPredicate<V>... values) {
predicates.add((criteria, cb, root) ->
cb.and(
Arrays.stream(values)
.map(t -> t.apply(criteria, cb))
.map(subquery -> root.get(attribute1).get(attribute2).in(subquery))
.toArray(Predicate[]::new)
)
);
return self();
}
@SafeVarargs
@Override
public final <P1, P2, V> Q in(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, V> attribute3, SubqueryPredicate<V>... values) {
predicates.add((criteria, cb, root) ->
cb.and(
Arrays.stream(values)
.map(t -> t.apply(criteria, cb))
.map(subquery -> root.get(attribute1).get(attribute2).get(attribute3).in(subquery))
.toArray(Predicate[]::new)
)
);
return self();
}
}
Lastly, to finish the implementation, we can provide static in()
methods, allowing us to create nested IN predicates within the and/or filters.
For example, the following query will retrieve the orders with a customer matching any of the provided subqueries:
List<Order> ordersToSpainOrWithActiveMembership = criteriaApiHelper.select(Order.class)
.or(Order_.customer,
in(
subquery(CustomerDetails.class)
.isTrue(CustomerDetails_.isActiveMembership)
.select(CustomerDetails_.customer)
),
in(Customer_.address,
subquery(AddressDetails.class)
.equal(AddressDetails_.country, Country_.name, "Spain")
.select(AddressDetails_.address)
)
)
.findAll();
There is the implementation for all the static in()
methods in the CriteriaApiHelper
class:
public class CriteriaApiHelper {
...
@SafeVarargs
public static <R> QueryPart<R> in(SubqueryPredicate<R>... values) {
return new QueryPart<R>()
.in(values);
}
@SafeVarargs
public static <R, V> QueryPart<R> in(SingularAttribute<R, V> attribute, SubqueryPredicate<V>... values) {
return new QueryPart<R>()
.in(attribute, values);
}
@SafeVarargs
public static <R, P, V> QueryPart<R> in(SingularAttribute<R, P> attribute1, SingularAttribute<P, V> attribute2,
SubqueryPredicate<V>... values) {
return new QueryPart<R>()
.in(attribute1, attribute2, values);
}
@SafeVarargs
public static <R, P1, P2, V> QueryPart<R> in(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, V> attribute3, SubqueryPredicate<V>... values) {
return new QueryPart<R>()
.in(attribute1, attribute2, attribute3, values);
}
}
In this part, we slightly improved extension for the Criteria API. Now we can perform the IN operator.