Skip to content

Commit

Permalink
Merge pull request #266 from njr-11/255-define-parameter-based-method…
Browse files Browse the repository at this point in the history
…s-in-spec-doc

Specify parameter-based queries/methods in spec doc
  • Loading branch information
otaviojava authored Sep 20, 2023
2 parents 8f5c8bc + a83a9d2 commit 12ca283
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 50 deletions.
85 changes: 43 additions & 42 deletions api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import jakarta.data.Limit;
import jakarta.data.Sort;
import jakarta.data.exceptions.OptimisticLockingFailureException;
import jakarta.data.page.Pageable;
import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.DataRepository;
Expand Down Expand Up @@ -169,17 +170,9 @@
* <td>for delete operations</td>
* <td><code>public void delete(person);</code></td></tr>
*
* <tr style="vertical-align: top"><td><code>insert</code></td>
* <td>creates new entities</td>
* <td><code>public void insertNewHires(Collection&lt;Employee&gt; newEmployees);</code></td></tr>
*
* <tr style="vertical-align: top"><td><code>save</code></td>
* <td>update if exists, otherwise insert</td>
* <td><code>Product[] saveAll(Product... products)</code></td></tr>
*
* <tr style="vertical-align: top"><td><code>update</code></td>
* <td>updates an existing entity</td>
* <td><code>public Product update(Product modifiedProduct);</code></td></tr>
* </table>
*
* <p>Repository methods following the <b>Query by Method Name</b> pattern
Expand Down Expand Up @@ -532,23 +525,6 @@
* <td><code>LinkedHashMap&lt;K, E&gt;</code></td>
* <td>Ordered map of Id attribute value to entity</td></tr>
*
* <tr style="vertical-align: top"><td><code>insert(E)</code></td>
* <td><code>E</code>,
* <br><code>void</code>, <code>Void</code></td>
* <td>For inserting a single entity.</td></tr>
*
* <tr style="vertical-align: top"><td><code>insert(E...)</code>,
* <br><code>insert(Iterable&lt;E&gt;)</code></td>
* <td><code>void</code>, <code>Void</code>,
* <br><code>E[]</code>,
* <br><code>Iterable&lt;E&gt;</code>,
* <br><code>Stream&lt;E&gt;</code>,
* <br><code>Collection&lt;E&gt;</code>
* <br><code>Collection</code> subtypes</td>
* <td>For inserting multiple entities.
* <br>Collection subtypes must have a public default constructor
* and support <code>addAll</code> or <code>add</code></td></tr>
*
* <tr style="vertical-align: top"><td><code>save(E)</code></td>
* <td><code>E</code>,
* <br><code>void</code>, <code>Void</code></td>
Expand All @@ -566,25 +542,50 @@
* <td>For saving multiple entities.
* <br>Collection subtypes must have a public default constructor
* and support <code>addAll</code> or <code>add</code></td></tr>
*
* <tr style="vertical-align: top"><td><code>update(E)</code></td>
* <td><code>E</code>,
* <br><code>void</code>, <code>Void</code></td>
* <td>For updating a single entity.</td></tr>
*
* <tr style="vertical-align: top"><td><code>update(E...)</code>,
* <br><code>update(Iterable&lt;E&gt;)</code></td>
* <td><code>void</code>, <code>Void</code>,
* <br><code>E[]</code>,
* <br><code>Iterable&lt;E&gt;</code>,
* <br><code>Stream&lt;E&gt;</code>,
* <br><code>Collection&lt;E&gt;</code>
* <br><code>Collection</code> subtypes</td>
* <td>For updating multiple entities.
* <br>Collection subtypes must have a public default constructor
* and support <code>addAll</code> or <code>add</code></td></tr>
* </table>
*
* <h2>Methods with Entity Parameters</h2>
*
* <p>You can define <i>save</i> and <i>delete</i>
* methods that accept entity parameters.</p>
*
* <h3>Save Methods</h3>
*
* <p>Save methods are a combination of update and insert
* where entities that are already present in the database are updated
* and entities that are not present in the database are inserted.</p>
*
* <p>The unique identifier is used to determine if an entity exists in the database.
* If the entity exists in the database and the entity is versioned
* (for example, with {@code @jakarta.persistence.Version} or by another convention
* from the entity model such as having an attribute named {@code version}),
* then the version must also match. When updates are saved to the database,
* the version is automatically incremented. If the version does not match,
* the <i>save</i> method raises {@link OptimisticLockingFailureException}.</p>
*
* <p>A <i>save</i> method parameter that supplies multiple entities
* might end up updating some and inserting others in the database.</p>
*
* <p><b><i>Generated Values</i></b>
* <br>When saving to the database, some entity attributes might be automatically
* generated or automatically incremented in the database.
* To obtain these values, define the return type of the <i>save</i> method to be
* the entity type or a type that is a collection or array of the entity.
* Entities that are returned by <i>save</i> methods include updates that
* were made to the entity. No guarantees are made regarding the state of entity
* instances that are supplied as parameters to the method after the method ends.</p>
*
* <h3>Delete Methods</h3>
*
* <p>Delete methods remove entities from the database based on the
* unique identifier of the entity parameter value. If the entity is versioned
* (for example, with {@code @jakarta.persistence.Version} or by another convention
* from the entity model such as having an attribute named {@code version}),
* then the version must also match. Other entity attributes do not need to match.
* The the unique identifier of an entity is not found in the database or its
* version does not match, the <i>delete</i> method raises
* {@link OptimisticLockingFailureException}.</p>
*
* <h2>Parameters to Repository Query Methods</h2>
*
* <p>The parameters to the {@code find}, {@code exists}, {@code count},
Expand Down
90 changes: 82 additions & 8 deletions spec/src/main/asciidoc/repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Several vital characteristics define repositories:

- **Built-In Interfaces:** The Jakarta Data specification provides a set of built-in interfaces from which repositories can inherit. These built-in interfaces offer a convenient way to include a variety of pre-defined methods for common operations. They also declare the entity type to use for methods where the entity type cannot otherwise be inferred.

- **Data Retrieval and Modification:** Repositories facilitate data retrieval and modification operations. This includes querying for persistent instances in the data store, creating new persistent instances in the data store, removing existing persistent instances, and modifying the state of persistent instances. Conventionally, these operations are named insert, delete, and update for modifying operations and find, count, and exists for retrieval operations.
- **Data Retrieval and Modification:** Repositories facilitate data retrieval and modification operations. This includes querying for persistent instances in the data store, creating new persistent instances in the data store, removing existing persistent instances, and modifying the state of persistent instances. Conventionally, these operations are named save and delete for modifying operations and find, count, and exists for retrieval operations.

- **Subset of Data:** Repositories may expose only a subset of the full data set available in the data store, providing a focused and controlled access point to the data.

Expand Down Expand Up @@ -162,7 +162,7 @@ A variety of basic types can be used for fields or properties of entity classes.
|===


Every entity in Jakarta Data must have a unique identifier composed of one or more supported basic types. This unique identifier is crucial for distinguishing individual entities in the database. Entity models that are used with Jakarta Data must define a way for developers to specify the unique identifier. Typically this is done with an `@Id` annotation, but other means are permitted, such as `@EmbeddedId` in Jakarta Persistence which defines a compound unique identifier based on an embeddable class, or by naming convention (for example, considering a property to be the unique identifier if is named id or ends in Id).
Every entity in Jakarta Data must have a unique identifier composed of one or more supported basic types. This unique identifier is crucial for distinguishing individual entities in the database. Entity models that are used with Jakarta Data must define a way for developers to specify the unique identifier. Typically this is done with an `@Id` annotation, but other means are permitted, such as `@EmbeddedId` in Jakarta Persistence which defines a compound unique identifier based on an embeddable class, or by naming convention (for example, considering a property to be the unique identifier if it is named id or ends in Id).

IMPORTANT: It's important to note that key-value, wide-column, document, and relational databases support Collection specializations and Maps of the basic types. However, these databases may have different serialization processes, impacting performance and causing impedance mismatch. Developers should consider these side effects when working with collections and maps in their entity models.

Expand Down Expand Up @@ -384,10 +384,11 @@ In instances where a Jakarta Data provider for NoSQL databases encounters a recu

=== Query Methods

In Jakarta Data, besides finding by an ID, custom queries can be written in two ways:
In Jakarta Data, besides finding by an ID, custom queries can be written in three ways:

* `@Query` annotation: Defines a query string in the annotation.
* Query by method name: Defines a query based on naming convention used in the method name.
* Query by Method Name: Defines a query based on naming conventions used in the method name.
* Query by Parameters: Defines a query based on the method parameter names and a method name prefix.

WARNING: Due to the variety of data sources, those resources might not work; it varies based on the Jakarta Data implementation and the database engine, which can provide queries on more than a Key or ID or not, such as a Key-value database.

Expand Down Expand Up @@ -416,7 +417,7 @@ public interface ProductRepository extends CrudRepository<Product, Long> {
----


==== Query by Method
==== Query by Method Name

The Query by method mechanism allows for creating query commands by naming convention.

Expand Down Expand Up @@ -611,7 +612,7 @@ WARNING: Define as a priority following standard Java naming conventions, camel

In queries by method name, `Id` is an alias for the entity property that is designated as the id. Entity property names that are used in queries by method name must not contain reserved words.

===== Query Methods Keywords
===== Query by Method Name Keywords

The following table lists the query-by-method keywords that must be supported by Jakarta Data providers, except where explicitly indicated for a type of database.

Expand Down Expand Up @@ -774,7 +775,37 @@ Wildcard characters for patterns are determined by the data access provider. For

For relational databases, the logical operator `And` takes precedence over `Or`, meaning that `And` is evaluated on conditions before `Or` when both are specified on the same method. For other database types, the precedence is limited to the capabilities of the database. For example, some graph databases are limited to precedence in traversal order.

=== Special Parameter Handling
==== Query by Parameters

The Query by Parameters pattern determines the query conditions from the names of the method's parameters that are not of type `Limit`, `Sort`, and `Pageable`. Each query condition is an equality comparison, comparing the parameter value against the value of the entity attribute whose name matches the method parameter name. For embedded attributes, the `_` character is used as the delimiter in the method parameter name. All query conditions are implicitly joined by the `And` operator, such that all must match for an entity to be considered matching.

A method name prefix, either `find` or `exists` or `count` or `delete`, specifies the type of query to be performed. The remainder of the method name can be any valid characters, except that it must not include the `By` keyword, which is what distinguishes it from Query by Method Name.

Query by Parameters relies on parameter names that are unavailable at run time unless the developer specifies the `-parameters` compiler option. If the Jakarta Data provider does not process repositories at build time, the developer must specify the compiler option to use Query by Parameters.

Example repository methods that use Query by Parameters:

[source,java]
----
@Repository
public interface ProductRepository extends CrudRepository<Product, Long> {
// Assumes that the Product entity has attributes: yearProduced
List<Product> findMadeIn(int yearProduced, Sort... sorts);
// Assumes that the Product entity has attributes: name, status.
boolean existsWithStatus(String name, Status status);
// Assumes that the Product entity has attributes: yearProduced
void deleteOutdated(int yearProduced);
}
----

After the query condition parameters, Query by Parameters `find` methods can include additional parameters of the types listed in the section "Special Parameter Handling".

Refer to the Jakarta Data module JavaDoc section on "Return Types for Repository Methods" for a listing of valid return types for Query by Parameters methods.

==== Special Parameter Handling

Jakarta Data also supports particular parameters to define pagination and sorting.

Expand Down Expand Up @@ -808,6 +839,46 @@ first20 = products.findByNameLike(name, pageable);
----

=== Methods With Entity Parameters

Repository methods with a name that begins with one of the prefixes, `save` or `delete`, can have a single entity parameter that is one of the following types, where `E` is the entity type:

- `E` - the entity type
- `E[]` - an array of the entity type
- `E...` - a variable arguments array of the entity type
- `Iterable<E>` and subtypes of `Iterable<E>` - a collection of multiple entities

Note: A form of `delete` can be defined in a different manner under the Query by Parameters and Query by Method Name patterns. In those cases, the method can have multiple parameters, none of which can be the entity type.

==== Save Methods

Save methods are a combination of update and insert where entities that are already present in the database are updated and entities that are not present in the database are inserted.

The unique identifier is used to determine if an entity exists in the database. If the entity exists in the database and the entity is versioned (for example, with `@code jakarta.persistence.Version` or by another convention from the entity model such as having an attribute named `version`), then the version must also match. When updates are saved to the database, the version is automatically incremented. If the version does not match, the `save` method must raise `OptimisticLockingFailureException`.

A `save` method parameter that supplies multiple entities might end up updating some and inserting others in the database.

===== Generated Values

When saving to the database, some entity attributes might be automatically generated or automatically incremented in the database. To obtain these values, the user can define the `save` method to return the entity type or a type that is a collection or array of the entity. Entities that are returned by `save` methods must include updates that were made to the entity. Jakarta Data does not require updating instances of entities that are supplied as parameters to the method.

Example usage:

[source,java]
----
product.setPrice(15.99);
product = products.save(product);
System.out.println("Saved version " + product.getVersion() + " of " + product);
----

==== Delete Methods

Delete methods remove entities from the database based on the unique identifier of the entity parameter value. If the entity is versioned (for example, with `@code jakarta.persistence.Version` or by another convention from the entity model such as having an attribute named `version`), then the version must also match. Other entity attributes do not need to match. The the unique identifier of an entity is not found in the database or its version does not match, a `delete` method with a return type of `void` or `Void` must raise `OptimisticLockingFailureException`.

==== Return Types

Refer to the Jakarta Data module JavaDoc section on "Return Types for Repository Methods" for a listing of valid return types for methods with entity parameters.

=== Precedence of Sort Criteria

The specification defines different ways of providing sort criteria on queries. This section discusses how these different mechanisms relate to each other.
Expand Down Expand Up @@ -976,10 +1047,13 @@ When composing repository methods in Jakarta Data, there are a several different
| Otherwise, if the method is annotated with `@Query`, the query from the annotation is used.
| The `@Query` annotation defines a custom query.

| Otherwise, if the method has a single parameter with a type that is the entity type or array, Iterable, or Iterable subclass of the entity type, determine the operation according to the method name prefix, which can be `save` or `delete`.
| Methods with entity parameters define operations on one or more entities.

| Otherwise, if the method name contains the `By` keyword, determine the query according to the BNF for Query by Method Name.
| Query by method name allows dynamic query generation based on method names and parameters.

| Otherwise, process it as Query by Parameters, determining the query from the supplied parameters and the `insert`, `update`, `delete`, `find`, `count`, or `exists` prefix.
| Otherwise, process it as Query by Parameters, determining the query from the supplied parameters and the `delete`, `find`, `count`, or `exists` prefix.
| Query by parameters constructs queries based on method parameters and prefixes.
|===

Expand Down

0 comments on commit 12ca283

Please sign in to comment.