paint-brush
Improving Your Experience With CriteriaAPI Using Builder Pattern and JPA Static Metamodel - Part IIIby@alexandermakeev
610 reads
610 reads

Improving Your Experience With CriteriaAPI Using Builder Pattern and JPA Static Metamodel - Part III

by Alexander MakeevOctober 4th, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this chapter, we will continue implementing an extension for the Criteria API using Builder pattern and JPA Static Metamodel Generator to reduce complexity with explicitness and to increase readability

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Improving Your Experience With CriteriaAPI Using Builder Pattern and JPA Static Metamodel - Part III
Alexander Makeev HackerNoon profile picture

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.

1. SubqueryPredicate

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);
}

2. SubQuery

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.

3. IN Operator

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);
    }
}


4. Wrapping Up

In this part, we slightly improved extension for the Criteria API. Now we can perform the IN operator.