Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 72 additions & 18 deletions documentation/src/main/asciidoc/introduction/Interacting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1048,54 +1048,108 @@ In Hibernate 7, there's a new option, a very ergonomic API for programmatically
This new API:

- isn't part of the Criteria Query API, and so we don't need a `CriteriaQuery` object to make use of it,
- works with both HQL and Criteria queries, and even with <<named-queries,named HQL queries>>, and
- _does_ make use of the JPA <<metamodel-generator,static metamodel>> for type safety,
- works with both HQL and Criteria queries, and
- is optimized for the case of a query which returns its single root entity.

[source,java]
----
var query = session.createSelectionQuery("from Book where year(publicationDate) > 2000", Book.class);
if (titlePattern != null) {
query.addRestriction(Restriction.like(Book_.title, namePattern));
}
if (isbns != null && !isbns.isEmpty()) {
query.addRestriction(Restriction.in(Book_.isbn, isbns))
}
query.setOrder(List.of(Order.asc(Book_.title), Order.asc(Book_.isbn)));
List<Book> matchingBooks = query.getResultList();
var selection =
SelectionSpecification.create(Book.class,
// an optional base query, written in HQL:
"from Book where year(publicationDate) > 2000");

// add programmatic restrictions:
if (titlePattern != null)
selection.addRestriction(Restriction.like(Book_.title, namePattern));
if (isbns != null && !isbns.isEmpty())
selection.addRestriction(Restriction.in(Book_.isbn, isbns));

// add programmatic ordering:
if (orderByTitle) selection.addOrdering(Order.asc(Book_.title));
if (orderByIsbn) selection.addOrdering(Order.asc(Book_.isbn));

// add programmatic association fetching:
if (fetchPublisher) selection.addFetching(Path.from(Book.class).to(Book_.publisher));

// execute the query in the given session:
List<Book> matchingBooks = selection.createQuery(session).getResultList();
----

Notice that:

- The link:{doc-javadoc-url}org/hibernate/query/restriction/Restriction.html[`Restriction`] interface has static methods for constructing a variety of different kinds of restriction in a completely typesafe way.
- Similarly, the link:{doc-javadoc-url}org/hibernate/query/Order.html[`Order`] class has a variety of static methods for constructing different kinds of ordering criteria.

We need the following methods of `SelectionQuery`:
We need the following methods of link:{doc-javadoc-url}org/hibernate/query/programmatic/SelectionSpecification.html[`SelectionSpecification`]:

.Methods for query restriction and ordering
[%breakable,cols="30,~,^15"]
[%breakable,cols="20,~]
|===
| Method name | Purpose | JPA-standard
| Method name | Purpose

| `addRestriction()` | Add a restriction on the query results | &#10006;
| `setOrder()` | Specify how the query results should be ordered | &#10006;
| `addRestriction()` | Add a restriction on the query results
| `setOrder()`, `addOrder()` | Specify how the query results should be ordered
| `addFetching()` | Add a fetched association
| `addAugmentation()` | Add a custom function which directly manipulates the query
|===

Unfortunately, `Restriction` and `Order` can't be used with JPA's `TypedQuery` interface, and JPA has no built-in alternative, so if we're using `EntityManager`, we need to call `unwrap()` to obtain a `SelectionQuery`.

Alternatively, `Restriction` and `Order` can be used with <<paging-and-ordering,generated query or finder methods>>, and even with link:{doc-data-repositories-url}[Jakarta Data repositories].

The interface link:{doc-javadoc-url}org/hibernate/query/restriction/Path.html[`Path`] may be used to express restrictions on fields of an embedded or associated entity class.

[source,java]
----
List<Book> booksForPublisher =
session.createSelectionQuery("from Book", Book.class)
SelectionSpecification.create(Book.class)
.addRestriction(Path.from(Book.class).to(Book_.publisher).to(Publisher_.name)
.equalTo(publisherName))
.addFetching(Path.from(Book.class).to(Book_.publisher))
.createQuery(session)
.getResultList();
----

When `Restriction`, `Path`, and `Order` aren't expressive enough, we can _augment_ the query by manipulating its representation as a criteria:

[source,java]
----
var books =
SelectionSpecification.create(Book.class)
.addAugmentation((builder, query, book) ->
// augment the query via JPA Criteria API
query.where(builder.like(book.get(Book_.title), titlePattern)))
.orderBy(builder.asc(book.get(Book_.isbn)))
.createQuery(session)
.getResultList();
----

For really advanced cases, `addAugmentation()` works quite nicely with <<criteria-definition,`CriteriaDefinition`>>.

[source,java]
----
var books =
SelectionSpecification.create(Book.class)
.addAugmentation((builder, query, book) ->
// eliminate explicit references to 'builder'
new CriteriaDefinition<>(query) {{
where(like(entity.get(BasicEntity_.title), titlePattern),
greaterThan(book.get(Book_.pages), minPages));
orderBy(asc(book.get(Book_.isbn)));
}}
)
.createQuery(session)
.getResultList();
----

However, we emphasize that this API shines in cases where complex manipulations are _not_ required.

[NOTE]
====
`SelectionSpecification` (similar to its friend `MutationSpecification`) may be used in cases where a query returns a single "root" entity, possibly with some fetched associations.
It is not useful in cases where a query should return multiple entities, a projection of entity fields, or an aggregation.
For such cases, the full Criteria API is appropriate.
====

Programmatic restrictions, and especially programmatic ordering, are often used together with pagination.

[[pagination]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.programmatic.MutationSpecification;
import org.hibernate.query.programmatic.SelectionSpecification;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.spi.QueryProducerImplementor;
import org.hibernate.query.sql.spi.NativeQueryImplementor;
Expand Down Expand Up @@ -657,21 +655,6 @@ public MutationQuery createNamedMutationQuery(String name) {
return delegate.createNamedMutationQuery( name );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(String hql, Class<T> resultType) {
return delegate.createSelectionSpecification( hql, resultType );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(Class<T> rootEntityType) {
return delegate.createSelectionSpecification( rootEntityType );
}

@Override
public <T> MutationSpecification<T> createMutationSpecification(String hql, Class<T> mutationTarget) {
return delegate.createMutationSpecification( hql, mutationTarget );
}

@Override
public MutationQuery createNativeMutationQuery(String sqlString) {
return delegate.createNativeMutationQuery( sqlString );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.programmatic.MutationSpecification;
import org.hibernate.query.programmatic.SelectionSpecification;
import org.hibernate.stat.SessionStatistics;

import java.util.Collection;
Expand Down Expand Up @@ -750,21 +748,6 @@ public MutationQuery createNamedMutationQuery(String name) {
return this.lazySession.get().createNamedMutationQuery( name );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(String hql, Class<T> resultType) {
return this.lazySession.get().createSelectionSpecification( hql, resultType );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(Class<T> rootEntityType) {
return this.lazySession.get().createSelectionSpecification( rootEntityType );
}

@Override
public <T> MutationSpecification<T> createMutationSpecification(String hql, Class<T> mutationTarget) {
return this.lazySession.get().createMutationSpecification( hql, mutationTarget );
}

@SuppressWarnings("rawtypes")
@Override
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.programmatic.MutationSpecification;
import org.hibernate.query.programmatic.SelectionSpecification;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.spi.QueryProducerImplementor;
import org.hibernate.query.sql.spi.NativeQueryImplementor;
Expand Down Expand Up @@ -229,21 +227,6 @@ public MutationQuery createNamedMutationQuery(String name) {
return delegate.createNamedMutationQuery( name );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(String hql, Class<T> resultType) {
return delegate.createSelectionSpecification( hql, resultType );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(Class<T> rootEntityType) {
return delegate.createSelectionSpecification( rootEntityType );
}

@Override
public <T> MutationSpecification<T> createMutationSpecification(String hql, Class<T> mutationTarget) {
return delegate.createMutationSpecification( hql, mutationTarget );
}

@Override
public MutationQuery createNativeMutationQuery(String sqlString) {
return delegate.createNativeMutationQuery( sqlString );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,6 @@
import org.hibernate.query.hql.spi.SqmQueryImplementor;
import org.hibernate.query.named.NamedObjectRepository;
import org.hibernate.query.named.NamedResultSetMappingMemento;
import org.hibernate.query.programmatic.MutationSpecification;
import org.hibernate.query.programmatic.SelectionSpecification;
import org.hibernate.query.programmatic.internal.MutationSpecificationImpl;
import org.hibernate.query.programmatic.internal.SelectionSpecificationImpl;
import org.hibernate.query.spi.HqlInterpretation;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.sql.internal.NativeQueryImpl;
Expand Down Expand Up @@ -1260,21 +1256,6 @@ public MutationQuery createNamedMutationQuery(String queryName) {
memento -> createNativeQueryImplementor( queryName, memento ) );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(String hql, Class<T> resultType) {
return new SelectionSpecificationImpl<>( hql, resultType, this );
}

@Override
public <T> SelectionSpecification<T> createSelectionSpecification(Class<T> rootEntityType) {
return new SelectionSpecificationImpl<>( rootEntityType, this );
}

@Override
public <T> MutationSpecification<T> createMutationSpecification(String hql, Class<T> mutationTarget) {
return new MutationSpecificationImpl<>( hql, mutationTarget, this );
}

protected <T> NativeQueryImplementor<T> createNativeQueryImplementor(String queryName, NamedNativeQueryMemento<T> memento) {
final NativeQueryImplementor<T> query = memento.toQuery( this );
final Boolean isUnequivocallySelect = query.isSelectQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import org.hibernate.Incubating;
import org.hibernate.query.criteria.JpaCriteriaInsert;
import org.hibernate.query.programmatic.MutationSpecification;
import org.hibernate.query.programmatic.SelectionSpecification;

/**
* Contract for things that can produce instances of {@link Query} and {@link NativeQuery}.
Expand Down Expand Up @@ -499,65 +496,6 @@ public interface QueryProducer {
*/
MutationQuery createNamedMutationQuery(String name);

/**
* Returns a specification reference which can be used to programmatically,
* iteratively build a {@linkplain SelectionQuery} based on a base HQL statement,
* allowing the addition of {@linkplain SelectionSpecification#addOrdering sorting}
* and {@linkplain SelectionSpecification#addRestriction restrictions}.
*
* @param hql The base HQL query.
* @param resultType The result type which will ultimately be returned from the {@linkplain SelectionQuery}
*
* @param <T> The root entity type for the query.
* {@code resultType} and {@code <T>} are both expected to refer to a singular query root.
*
* @throws IllegalSelectQueryException The given HQL is expected to be a {@code select} query. This method will
* throw an exception if not.
*
* @since 7.0
*/
@Incubating
<T> SelectionSpecification<T> createSelectionSpecification(String hql, Class<T> resultType)
throws IllegalSelectQueryException;

/**
* Returns a specification reference which can be used to programmatically,
* iteratively build a {@linkplain SelectionQuery} for the given entity type,
* allowing the addition of {@linkplain SelectionSpecification#addOrdering sorting}
* and {@linkplain SelectionSpecification#addRestriction restrictions}.
* This is effectively the same as calling {@linkplain QueryProducer#createSelectionSpecification(String, Class)}
* with {@code "from {rootEntityType}"} as the HQL.
*
* @param rootEntityType The entity type which is the root of the query.
*
* @param <T> The entity type which is the root of the query.
* {@code resultType} and {@code <T>} are both expected to refer to a singular query root.
*
* @since 7.0
*/
@Incubating
<T> SelectionSpecification<T> createSelectionSpecification(Class<T> rootEntityType);

/**
* Returns a specification reference which can be used to programmatically,
* iteratively build a {@linkplain MutationQuery} based on a base HQL statement,
* allowing the addition of {@linkplain MutationSpecification#addRestriction restrictions}.
*
* @param hql The base HQL query (expected to be an {@code update} or {@code delete} query).
* @param mutationTarget The entity which is the target of the mutation.
*
* @param <T> The root entity type for the mutation (the "target").
* {@code mutationTarget} and {@code <T>} are both expected to refer to the mutation target.
*
* @throws IllegalMutationQueryException Only {@code update} and {@code delete} are supported;
* this method will throw an exception if the given HQL query is not an {@code update} or {@code delete}.
*
* @since 7.0
*/
@Incubating
<T> MutationSpecification<T> createMutationSpecification(String hql, Class<T> mutationTarget)
throws IllegalMutationQueryException;

/**
* Create a {@link Query} instance for the named query.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ public CriteriaDefinition(SessionFactory factory, CriteriaQuery<R> baseQuery) {
query = (JpaCriteriaQuery<R>) baseQuery;
}

public CriteriaDefinition(CriteriaQuery<R> baseQuery) {
super( ((JpaCriteriaQuery<R>) baseQuery).getCriteriaBuilder() );
query = (JpaCriteriaQuery<R>) baseQuery;
}

public CriteriaDefinition(EntityManagerFactory factory, Class<R> resultType) {
super( factory.getCriteriaBuilder() );
query = createQuery( resultType );
Expand Down
Loading