16. Criteria
Criteria queries offer a type-safe alternative to HQL, JPQL and native SQL queries.
Hibernate offers an older, legacy No feature development will target those APIs. Eventually, Hibernate-specific Criteria features will be ported as extensions to the JPA This chapter will focus on the JPA APIs for declaring type-safe criteria queries. |
Criteria queries are a programmatic, type-safe way to express a query. They are type-safe in terms of using interfaces and classes to represent various structural parts of a query such as the query itself, the select clause, or an order-by, etc. They can also be type-safe in terms of referencing attributes as we will see in a bit. Users of the older Hibernate org.hibernate.Criteria
query API will recognize the general approach, though we believe the JPA API to be superior as it represents a clean look at the lessons learned from that API.
Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of the query. The first step in performing a criteria query is building this graph. The javax.persistence.criteria.CriteriaBuilder
interface is the first thing with which you need to become acquainted with before using criteria queries. Its role is that of a factory for all the individual pieces of the criteria. You obtain a javax.persistence.criteria.CriteriaBuilder
instance by calling the getCriteriaBuilder()
method of either javax.persistence.EntityManagerFactory
or javax.persistence.EntityManager
.
The next step is to obtain a javax.persistence.criteria.CriteriaQuery
. This is accomplished using one of the three methods on javax.persistence.criteria.CriteriaBuilder
for this purpose:
<T> CriteriaQuery<T> createQuery( Class<T> resultClass )
CriteriaQuery<Tuple> createTupleQuery()
CriteriaQuery<Object> createQuery()
Each serves a different purpose depending on the expected type of the query results.
The chapter 6 (i.e., Criteria API) of the JPA Specification already contains a decent amount of reference material pertaining to the various parts of a criteria query. So rather than duplicate all that content here, let’s instead look at some of the more widely anticipated usages of the API. |
16.1. Typed criteria queries
The type of the criteria query (aka the <T>
) indicates the expected types in the query result. This might be an entity, an Integer
, or any other object.
16.2. Selecting an entity
This is probably the most common form of query. The application wants to select entity instances.
Example 557. Selecting the root entity
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
criteria.select( root );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Person> persons = entityManager.createQuery( criteria ).getResultList();
The example uses createQuery()
passing in the Person
class reference as the results of the query will be Person
objects.
The call to the The |
16.3. Selecting an expression
The simplest form of selecting an expression is selecting a particular attribute from an entity. But this expression might also represent an aggregation, a mathematical operation, etc.
Example 558. Selecting an attribute
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> criteria = builder.createQuery( String.class );
Root<Person> root = criteria.from( Person.class );
criteria.select( root.get( Person_.nickName ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<String> nickNames = entityManager.createQuery( criteria ).getResultList();
In this example, the query is typed as java.lang.String
because that is the anticipated type of the results (the type of the Person#nickName
attribute is java.lang.String
). Because a query might contain multiple references to the Person
entity, attribute references always need to be qualified. This is accomplished by the Root#get
method call.
16.4. Selecting multiple values
There are actually a few different ways to select multiple values using criteria queries. We will explore two options here, but an alternative recommended approach is to use tuples as described in Tuple criteria queries, or consider a wrapper query, see Selecting a wrapper for details.
Example 559. Selecting an array
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> criteria = builder.createQuery( Object[].class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.select( builder.array( idPath, nickNamePath ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Object[]> idAndNickNames = entityManager.createQuery( criteria ).getResultList();
Technically this is classified as a typed query, but you can see from handling the results that this is sort of misleading. Anyway, the expected result type here is an array.
The example then uses the array method of javax.persistence.criteria.CriteriaBuilder
which explicitly combines individual selections into a javax.persistence.criteria.CompoundSelection
.
Example 560. Selecting an array using multiselect
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> criteria = builder.createQuery( Object[].class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.multiselect( idPath, nickNamePath );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Object[]> idAndNickNames = entityManager.createQuery( criteria ).getResultList();
Just as we saw in Selecting an array we have a typed criteria query returning an Object
array. Both queries are functionally equivalent. This second example uses the multiselect()
method which behaves slightly differently based on the type given when the criteria query was first built, but, in this case, it says to select and return an Object[].
16.5. Selecting a wrapper
Another alternative to Selecting multiple values is to instead select an object that will “wrap” the multiple values. Going back to the example query there, rather than returning an array of [Person#id, Person#nickName], instead declare a class that holds these values and use that as a return object.
Example 561. Selecting a wrapper
public class PersonWrapper {
private final Long id;
private final String nickName;
public PersonWrapper(Long id, String nickName) {
this.id = id;
this.nickName = nickName;
}
public Long getId() {
return id;
}
public String getNickName() {
return nickName;
}
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonWrapper> criteria = builder.createQuery( PersonWrapper.class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.select( builder.construct( PersonWrapper.class, idPath, nickNamePath ) );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<PersonWrapper> wrappers = entityManager.createQuery( criteria ).getResultList();
First, we see the simple definition of the wrapper object we will be using to wrap our result values. Specifically, notice the constructor and its argument types. Since we will be returning PersonWrapper
objects, we use PersonWrapper
as the type of our criteria query.
This example illustrates the use of the javax.persistence.criteria.CriteriaBuilder
method construct which is used to build a wrapper expression. For every row in the result, we are saying we would like a PersonWrapper
instantiated with the remaining arguments by the matching constructor. This wrapper expression is then passed as the select.
16.6. Tuple criteria queries
A better approach to Selecting multiple values is to use either a wrapper (which we just saw in Selecting a wrapper) or using the javax.persistence.Tuple
contract.
Example 562. Selecting a tuple
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> root = criteria.from( Person.class );
Path<Long> idPath = root.get( Person_.id );
Path<String> nickNamePath = root.get( Person_.nickName);
criteria.multiselect( idPath, nickNamePath );
criteria.where( builder.equal( root.get( Person_.name ), "John Doe" ) );
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
for ( Tuple tuple : tuples ) {
Long id = tuple.get( idPath );
String nickName = tuple.get( nickNamePath );
}
//or using indices
for ( Tuple tuple : tuples ) {
Long id = (Long) tuple.get( 0 );
String nickName = (String) tuple.get( 1 );
}
This example illustrates accessing the query results through the javax.persistence.Tuple
interface. The example uses the explicit createTupleQuery()
of javax.persistence.criteria.CriteriaBuilder
. An alternate approach is to use createQuery( Tuple.class )
.
Again we see the use of the multiselect()
method, just like in Selecting an array using multiselect
. The difference here is that the type of the javax.persistence.criteria.CriteriaQuery
was defined as javax.persistence.Tuple
so the compound selections, in this case, are interpreted to be the tuple elements.
The javax.persistence.Tuple contract provides three forms of access to the underlying elements:
typed
The Selecting a tuple example illustrates this form of access in the tuple.get( idPath )
and tuple.get( nickNamePath )
calls. This allows typed access to the underlying tuple values based on the javax.persistence.TupleElement
expressions used to build the criteria.
positional
Allows access to the underlying tuple values based on the position. The simple Object get(int position) form is very similar to the access illustrated in Selecting an array and Selecting an array using multiselect
. The
aliased
Allows access to the underlying tuple values based on (optionally) assigned alias. The example query did not apply an alias. An alias would be applied via the alias method on javax.persistence.criteria.Selection
. Just like positional
access, there is both a typed (Object get(String alias)) and an untyped (
16.7. FROM clause
A CriteriaQuery
object defines a query over one or more entity, embeddable, or basic abstract schema types. The root objects of the query are entities, from which the other types are reached by navigation.
— JPA Specification, section 6.5.2 Query Roots, pg 262
All the individual parts of the FROM clause (roots, joins, paths) implement the |
16.8. Roots
Roots define the basis from which all joins, paths and attributes are available in the query. A root is always an entity type. Roots are defined and added to the criteria by the overloaded from methods on javax.persistence.criteria.CriteriaQuery
:
Example 563. Root methods
<X> Root<X> from( Class<X> );
<X> Root<X> from( EntityType<X> );
Example 564. Adding a root example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
Criteria queries may define multiple roots, the effect of which is to create a Cartesian Product between the newly added root and the others. Here is an example defining a Cartesian Product between Person
and Partner
entities:
Example 565. Adding multiple roots example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> personRoot = criteria.from( Person.class );
Root<Partner> partnerRoot = criteria.from( Partner.class );
criteria.multiselect( personRoot, partnerRoot );
Predicate personRestriction = builder.and(
builder.equal( personRoot.get( Person_.address ), address ),
builder.isNotEmpty( personRoot.get( Person_.phones ) )
);
Predicate partnerRestriction = builder.and(
builder.like( partnerRoot.get( Partner_.name ), prefix ),
builder.equal( partnerRoot.get( Partner_.version ), 0 )
);
criteria.where( builder.and( personRestriction, partnerRestriction ) );
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
16.9. Joins
Joins allow navigation from other javax.persistence.criteria.From
to either association or embedded attributes. Joins are created by the numerous overloaded join methods of the javax.persistence.criteria.From
interface.
Example 566. Join example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Phone> criteria = builder.createQuery( Phone.class );
Root<Phone> root = criteria.from( Phone.class );
// Phone.person is a @ManyToOne
Join<Phone, Person> personJoin = root.join( Phone_.person );
// Person.addresses is an @ElementCollection
Join<Person, String> addressesJoin = personJoin.join( Person_.addresses );
criteria.where( builder.isNotEmpty( root.get( Phone_.calls ) ) );
List<Phone> phones = entityManager.createQuery( criteria ).getResultList();
16.10. Fetches
Just like in HQL and JPQL, criteria queries can specify that associated data be fetched along with the owner. Fetches are created by the numerous overloaded fetch methods of the javax.persistence.criteria.From
interface.
Example 567. Join fetch example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Phone> criteria = builder.createQuery( Phone.class );
Root<Phone> root = criteria.from( Phone.class );
// Phone.person is a @ManyToOne
Fetch<Phone, Person> personFetch = root.fetch( Phone_.person );
// Person.addresses is an @ElementCollection
Fetch<Person, String> addressesJoin = personFetch.fetch( Person_.addresses );
criteria.where( builder.isNotEmpty( root.get( Phone_.calls ) ) );
List<Phone> phones = entityManager.createQuery( criteria ).getResultList();
Technically speaking, embedded attributes are always fetched with their owner. However, in order to define the fetching of Phone#addresses we needed a |
16.11. Path expressions
Roots, joins and fetches are themselves path expressions as well. |
16.12. Using parameters
Example 568. Parameters example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> root = criteria.from( Person.class );
ParameterExpression<String> nickNameParameter = builder.parameter( String.class );
criteria.where( builder.equal( root.get( Person_.nickName ), nickNameParameter ) );
TypedQuery<Person> query = entityManager.createQuery( criteria );
query.setParameter( nickNameParameter, "JD" );
List<Person> persons = query.getResultList();
Use the parameter method of javax.persistence.criteria.CriteriaBuilder
to obtain a parameter reference. Then use the parameter reference to bind the parameter value to the javax.persistence.Query
.
16.13. Using group by
Example 569. Group by example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createQuery( Tuple.class );
Root<Person> root = criteria.from( Person.class );
criteria.groupBy(root.get("address"));
criteria.multiselect(root.get("address"), builder.count(root));
List<Tuple> tuples = entityManager.createQuery( criteria ).getResultList();
for ( Tuple tuple : tuples ) {
String name = (String) tuple.get( 0 );
Long count = (Long) tuple.get( 1 );
}