Skip to content

Commit

Permalink
document dynamic restrictions in Jakarta Data repos
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinking committed Jan 21, 2025
1 parent bd557ef commit 226fd9a
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 4 deletions.
70 changes: 66 additions & 4 deletions documentation/src/main/asciidoc/repositories/Pagination.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ An <<find-method,automatic>> or <<query-method,annotated>> query method may have

Before we see this, let's see how we can refer to a field of an entity in a completely typesafe way.

[[data-static-metamodel]]
=== The static metamodel

You might already be familiar with the Jakarta Persistence static metamodel.
Expand Down Expand Up @@ -59,6 +60,7 @@ This example looks superficially more typesafe.
But since Hibernate Data Repositories already validates the content of the `@OrderBy` annotation at compile time, it's not really better.
====

[[dynamic-sorting]]
=== Dynamic sorting

Dynamic sorting criteria are expressed using the types `Sort` and `Order`:
Expand Down Expand Up @@ -100,7 +102,7 @@ The method might now be called like this:
var books =
library.books(pattern, year,
Order.of(_Book.title.ascIgnoreCase(),
_Book.isbn.asc());
_Book.isbn.asc()));
----

Dynamic sorting criteria may be combined with static criteria.
Expand All @@ -115,6 +117,7 @@ List<Book> books(@Pattern String title, Year yearPublished,

We're not convinced this is very useful in practice.

[[limits]]
=== Limits

A `Limit` is the simplest way to express a subrange of query results.
Expand All @@ -141,6 +144,7 @@ var books =

A more sophisticated approach is provided by `PageRequest`.

[[offset-based-pagination]]
=== Offset-based pagination

A `PageRequest` is superficially similar to a `Limit`, except that it's specified in terms of:
Expand Down Expand Up @@ -218,6 +222,7 @@ We'll refer to this as _offset-based pagination_.
A problem with this approach is that it's quite vulnerable to missed or duplicate results when the database is modified between page requests.
Therefore, Jakarta Data offers an alternative solution, which we'll call _key-based pagination_.

[[key-based-pagination]]
=== Key-based pagination

In key-based pagination, the query results must be totally ordered by a unique key of the result set.
Expand All @@ -241,7 +246,7 @@ The difference is that we must declare our repository method to return `Cursored
@OrderBy("title")
@OrderBy("isbn")
CursoredPage<Book> books(@Pattern String title, Year yearPublished,
PageRequest pageRequest);
PageRequest pageRequest);
----

On the other hand, with key-based pagination, Hibernate must do some work under the covers rewriting our query.
Expand All @@ -258,6 +263,63 @@ Direct API support for key-based pagination originated in the work of Hibernate
It was adopted from there by the Jakarta Data specification, and is now even available in Hibernate ORM via the link:{doc-javadoc-url}org/hibernate/query/KeyedPage.html[`KeyedPage`]/link:{doc-javadoc-url}org/hibernate/query/KeyedResultList.html[`KeyedResultList`] API.
****

[[dynamic-restrictions]]
=== Dynamic restrictions

Jakarta Data 1.0 does not include an API for programmatically specifying restrictions, but for now we may use the native link:{doc-javadoc-url}org/hibernate/query/restriction/Restriction.html[`Restriction`] API in Hibernate 7.

[NOTE]
====
Restrictions will be standardized by Jakarta Data 1.1.
====

Hibernate, an atomic `Restriction` is formed from:

- a reference to a JPA `SingularAttribute`, usually obtained via the _Jakarta Persistence_ (not Jakarta Data) static metamodel, together with
- a `Range` of allowed values for that attribute.

A query method may have a parameter of type `Restriction`, for example:

[source,java]
----
@Find
List<Book> books(Restriction<Book> restriction,
Order<Book> order);
----

This method would be called like this:

[source,java]
----
var books =
library.books(Restriction.contains(Book_.title, "Hibernate"),
Order.of(_Book.title.ascIgnoreCase(),
_Book.isbn.asc()));
----

Notice the mix of metamodels here: `Book_` is the Persistence metamodel, and `_Book` is the Data metamodel.

It's even possible to directly use a link:{doc-javadoc-url}org/hibernate/query/range/Range.html[`Range`] to restrict a given property or field of an entity:

[source,java]
----
@Find
List<Book> books(Range<String> title, Range<Year> yearPublished,
Order<Book> order);
----

There are various kinds of `Range`, including lists, patterns, and intervals:

[source,java]
----
var books =
library.books(Range.prefix("Hibernate"),
Range.closed(Year.of(2000), Year.of(2009)),
Order.of(_Book.title.ascIgnoreCase(),
_Book.isbn.asc()));
----

[[advanced-query-control]]
=== Advanced control over querying

For more advanced usage, an automatic or annotated query method may be declared to return `jakarta.persistence.Query`, `jakarta.persistence.TypedQuery`, link:{doc-javadoc-url}org/hibernate/query/Query.html[`org.hibernate.query.Query`], or link:{doc-javadoc-url}org/hibernate/query/SelectionQuery.html[`org.hibernate.query.SelectionQuery`].
Expand All @@ -269,11 +331,11 @@ SelectionQuery<Book> booksQuery(@Pattern String title, Year yearPublished);
default List<Book> booksQuery(String title, Year yearPublished) {
return books(title, yearPublished)
.enableFetchProfile(_Book.PROFILE_WITH_AUTHORS);
.enableFetchProfile(_Book.PROFILE_WITH_AUTHORS)
.setReadOnly(true)
.setTimeout(QUERY_TIMEOUT)
.getResultList();
}
----

This allows for direct control over query execution, without loss of typesafety.
This allows for direct control over query execution, without loss of type safety.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ interface Library {

This is our first example of a repository.

[[repository-interfaces]]
=== Repository interfaces

A _repository interface_ is an interface written by you, the application programmer, and annotated `@Repository`.
Expand Down Expand Up @@ -165,6 +166,7 @@ We'll discuss each of these kinds of method soon.
But first we need to ask a more basic question: how are persistence operations organized into repositories, and how do repository interfaces relate to entity types?
The--perhaps surprising--answer is: it's completely up to you.

[[organizing-repository-operations]]
=== Organizing persistence operations

Jakarta Data lets you freely assign persistence operations to repositories according to your own preference.
Expand Down Expand Up @@ -536,6 +538,7 @@ List<String> booksByTitle(String pattern);

Unfortunately, native SQL queries cannot be validated at compile time, so if there's anything wrong with our SQL, we won't find out until we run our program.

[[by-and-param]]
=== `@By` and `@Param`

Query methods match method parameters to entity fields or query parameters by name.
Expand Down

0 comments on commit 226fd9a

Please sign in to comment.