Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify parameter-based queries/methods in spec doc #266

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 023 Contributors to the Eclipse Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package jakarta.data.exceptions;

/**
* Indicates that an entity cannot be inserted into the database
* because an entity with same unique identifier already exists in the database.
*/
public class EntityExistsException extends DataException {
private static final long serialVersionUID = -7275063477464065015L;

/**
* Constructs a new {@code EntityExistsException} with the specified detail message.
*
* @param message the detail message.
*/
public EntityExistsException(String message) {
super(message);
}

/**
* Constructs a new {@code EntityExistsException} with the specified detail message and cause.
*
* @param message the detail message.
* @param cause another exception or error that caused this exception.
* Null indicates that no other cause is specified.
*/
public EntityExistsException(String message, Throwable cause) {
super(message, cause);
}

/**
* Constructs a new {@code EntityExistsException} with the specified cause.
*
* @param cause the cause.
*/
public EntityExistsException(Throwable cause) {
super(cause);
}
}
99 changes: 77 additions & 22 deletions api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import jakarta.data.Limit;
import jakarta.data.Sort;
import jakarta.data.exceptions.EntityExistsException;
import jakarta.data.exceptions.OptimisticLockingFailureException;
import jakarta.data.page.Pageable;
import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.DataRepository;
Expand Down Expand Up @@ -179,7 +181,7 @@
*
* <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>
* <td><code>public boolean update(Product modifiedProduct);</code></td></tr>
* </table>
*
* <p>Repository methods following the <b>Query by Method Name</b> pattern
Expand Down Expand Up @@ -533,21 +535,13 @@
* <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><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>
* <td><code>void</code>, <code>Void</code></td>
* <td>For inserting multiple entities.</td></tr>
*
* <tr style="vertical-align: top"><td><code>save(E)</code></td>
* <td><code>E</code>,
Expand All @@ -568,23 +562,84 @@
* 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>
* <td><code>void</code>, <code>Void</code>
* <br><code>boolean</code>, <code>Boolean</code></td>
* <td>For updating a single entity.
* <br>A boolean result indicates whether or not the database was updated.</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>
* <br><code>boolean</code>, <code>Boolean</code>,
* <br><code>long</code>, <code>Long</code>,
* <br><code>int</code>, <code>Integer</code>,
* <br><code>short</code>, <code>Short</code>,
* <br><code>Number</code></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>
* <br>A boolean result indicates whether or not the database was updated.
* <br>A numeric result indicates how many entities were updated in the database.
* <br>Jakarta Persistence providers limit the maximum to <code>Integer.MAX_VALUE</code></td></tr>
* </table>
*
* <h2>Methods with Entity Parameters</h2>
*
* <p>You can define <i>insert</i>, <i>update</i>, <i>save</i>, and <i>delete</i>
* methods that accept entity parameters.</p>
*
* <h3>Insert Methods</h3>
*
* <p>Insert methods must create new entity instances in the database.
* If an entity already exists in the database with the same unique identifier,
* then the <i>insert</i> method raises {@link EntityExistsException}.</p>
*
* <h3>Update Methods</h3>
*
* <p>Update methods modify existing entities in the database based on the
* unique identifier of the entity parameter. 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. When updates are saved to the database,
* the version is automatically incremented. If a matching entity does not exist
* in the database, no update is made for that entity.
* The absence of a matching entity does not cause an error to be raised.</p>
*
* <h3>Save Methods</h3>
*
* <p>Save methods are a combination of <i>update</i> and <i>insert</i>
* 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
91 changes: 85 additions & 6 deletions spec/src/main/asciidoc/repository.asciidoc
Original file line number Diff line number Diff line change
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we got this discussion somewhere before; annotations such as "Find" could get more flexibility in this case:

https://docs.jboss.org/hibernate/orm/6.3/introduction/html_single/Hibernate_Introduction.html#generated-finder-methods

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it: #223

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we got this discussion somewhere before; annotations such as "Find" could get more flexibility in this case:

It's certainly true that including annotations could add more flexibility here. However, that's a separate proposal, or separate aspect of a proposal, to consider and discuss separately and is not intended to be addressed by this pull. The scope of this pull is to specify what is described under your pull #260 where it states "determining the query from the supplied parameters and the insert, update, delete, find, count, or exists prefix. Query by parameters constructs queries based on method parameters and prefixes."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I remember this PR, and that is my point: Does it make sense to have insert and update methods?
Should we update to use "DataAcessObject" instead of Repository?

IMHO: we should remove the insert and update and abstract with "save" for a while.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I remember this PR, and that is my point: Does it make sense to have insert and update methods? Should we update to use "DataAcessObject" instead of Repository?

IMHO: we should remove the insert and update and abstract with "save" for a while.

Well, I'm certainly not happy that I wasted all this effort documenting insert/update based on the fact that your pull added them so I thought you were in favor of them. But if you insist on removing them now, I'll update this pull to do so.

Copy link
Contributor Author

@njr-11 njr-11 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@otaviojava I removed insert and update for you: a83a9d2


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,54 @@ first20 = products.findByNameLike(name, pageable);

----

=== Methods With Entity Parameters

Repository methods with a name that begins with one of the prefixes, `insert`, `save`, `update`, `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` and `update` 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.

==== Insert Methods
otaviojava marked this conversation as resolved.
Show resolved Hide resolved

Insert methods must create new entity instances in the database. If an entity already exists in the database with the same unique identifier, then the `insert` method must raise `EntityExistsException`.

==== Update Methods

Update methods modify existing entities in the database based on the unique identifier of the entity parameter. 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. When updates are saved to the database, the version is automatically incremented. If a matching entity does not exist in the database, no update is made for that entity. The absence of a matching entity does not cause an error to be raised.

==== 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, the `delete` method 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