Skip to content

Commit

Permalink
switch Find value to the entity type and split out entity attribute n…
Browse files Browse the repository at this point in the history
…ames to a separate annotation
  • Loading branch information
njr-11 committed Jan 3, 2025
1 parent cb6c37a commit 4887c0d
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 52 deletions.
74 changes: 24 additions & 50 deletions api/src/main/java/jakarta/data/repository/Find.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,24 @@


/**
* <p>Annotates a repository method that returns entities or entity attributes
* as a parameter-based automatic query method.</p>
* <p>Annotates a repository method to be a parameter-based automatic query method
* that returns entities or entity attributes.</p>
*
* <p>The {@code Find} annotation indicates that the annotated repository
* method executes a query to retrieve entities or a subset of entity attributes
* based on its parameters and the arguments assigned to those parameters.
* The method return type identifies either:</p>
* method executes a query to retrieve entities (or a subset of entity attributes)
* based on its parameters and the arguments assigned to those parameters.</p>
*
* <p>The method return type identifies one of the following:</p>
* <ul>
* <li>the entity type returned by the query,</li>
* <li>a single entity attribute type returned by the query (requires
* {@link #value}), or</li>
* <li>a single entity attribute type returned by the query (requires the
* {@link Select} annotation), or</li>
* <li>a Java record type representing a subset of entity attributes returned
* by the query. The names of the record components and entity attributes
* must match, or the entity attribute names must be specified by
* {@link #value}.</li>
* must match, or the entity attribute names must be specified by the
* {@link Select} annotation.</li>
* </ul>
*
* <p>Repositories with methods that return a single entity attribute or a
* subset of entity attributes must specify a primary entity type because the
* method return type does not indicate the entity.</p>
*
* <p>Each parameter of the annotated method must either:
* </p>
* <ul>
Expand Down Expand Up @@ -126,51 +122,29 @@
@Target(ElementType.METHOD)
public @interface Find {
/**
* <p>Optionally specifies the name(s) of one or more entity attributes to
* fetch from the database.</p>
*
* <p>When a single entity attribute name is specified, the repository
* method returns instances of that entity attribute from entities of the
* primary entity type that match the restrictions imposed by the method
* parameters.</p>
* <p>Optionally specifies the entity type to look for in the database.</p>
*
* <p>For example, to return only the {@code price} attribute of the
* {@code Car} entity that has the specified {@code vin} attribute,</p>
* <p>The default value, {@code Object.class}, has the special meaning of
* determining the entity type from the method return type if the method
* returns entities, and otherwise from the primary entity type of the
* repository.</p>
*
* <pre>
* &#64;Repository
* public interface Cars extends BasicRepository&lt;Car, String&gt; {
* &#64;Find(_Car.PRICE)
* Optional&lt;Float&gt; getPrice(@By(_Car.VIN) String vehicleIdNum);
* }
* </pre>
* <p>A repository method with the {@code Find} annotation must specify a
* valid entity class as the value if the method does not return entities
* and the repository does not otherwise define a primary entity type.</p>
*
* <p>When multiple entity attribute names are specified, the repository
* method returns Java records that represent a subset of entity attributes.
* The order and types of the record components must match the order
* and types of the specified entity attribute names.</p>
* <p>For example,</p>
*
* <p>For example, to return only the {@code make}, {@code model}, and
* {@code year} attributes of a {@code Car} entity that has the specified
* {@code vin} attribute,</p>
* <pre>
* &#64;Repository
* public interface Cars extends BasicRepository&lt;Car, String&gt; {
* record ModelInfo(String model,
* String manufacturer,
* int designYear) {}
* public interface Vehicles {
* &#64;Find(Car.class)
* &#64;Select("price")
* Optional&lt;Float&gt; getPrice(@By("vin") String vehicleIdNum);
*
* &#64;Find({_Car.MODEL, _Car.MAKE, _Car.YEAR})
* Optional&lt;ModelInfo&gt; getModelInfo(@By(_Car.VIN) String vehicleIdNum);
* ...
* }
* </pre>
*
* <p>The examples above use the
* {@linkplain jakarta.data/jakarta.data.metamodel static metamodel},
* to avoid hard coding String values for the entity attribute names.</p>
*
* <p>When the list of entity attribute names is empty (which is the default),
* the general requirements defined by {@link Find} apply.</p>
*/
String[] value = {};
Class<?> value = Object.class;
}
128 changes: 128 additions & 0 deletions api/src/main/java/jakarta/data/repository/Select.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2025 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.repository;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <p>Determines which entity attribute(s) are returned by a {@link Find}
* operation.</p>
*
* <p>This annotation can be specified on a repository find method or
* on a record component.</p>
*
* <p>When used on a repository find method, the {@link value} of this annotation
* must be the name(s) of one or more entity attributes to use as query results
* when an entity matches the restrictions imposed by the method.</p>
*
* <p>When used on a record component, the {@link value} of this annotation
* must be the name of the entity attribute to which the record component
* corresponds. This value is used for all repository methods that lack this
* annotation and for which the record is the result type.</p>
*
* <p>This annotation must not be used in other locations.</p>
*
* @see Find
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.RECORD_COMPONENT })
public @interface Select {
/**
* <h2>Method that returns Single Entity Attributes</h2>
*
* <p>Place the {@code Select} annotation on a repository find method and
* assign the annotation value to be the name of a single entity attribute.
* The result type that is used in the method return type must be the
* type of the entity attribute.</p>
*
* <p>For example, to return only the {@code price} attribute of the
* {@code Car} entity that has the supplied {@code vin} attribute value,</p>
*
* <pre>
* &#64;Repository
* public interface Cars extends BasicRepository&lt;Car, String&gt; {
* &#64;Find
* &#64;Select(_Car.PRICE)
* Optional&lt;Float&gt; getPrice(@By(_Car.VIN) String vehicleIdNum);
* }
* </pre>
*
* <h2>Method that returns Java Records</h2>
*
* <p>A repository method can return a subset of entity attributes per result
* by having the result type be a Java record. The {@code Select} annotation
* can be used in the following ways to accommodate this.</p>
*
* <h3>Annotating a Repository Method</h3>
*
* <p>Place the {@code Select} annotation on a repository find method and
* assign the annotation value to be the names of multiple entity attributes,
* corresponding to the order and types of the components of the Java record
* that is used for the result type.</p>
*
* <p>For example, to return only the {@code model}, {@code make}, and
* {@code year} attributes of a {@code Car} entity that has the supplied
* {@code vin} attribute value,</p>
*
* <pre>
* &#64;Repository
* public interface Cars extends BasicRepository&lt;Car, String&gt; {
* record ModelInfo(String model,
* String manufacturer,
* int designYear) {}
*
* &#64;Find
* &#64;Select({_Car.MODEL, _Car.MAKE, _Car.YEAR})
* Optional&lt;ModelInfo&gt; getModelInfo(@By(_Car.VIN) String vehicleIdNum);
* }
* </pre>
*
* <h3>Annotating a Record Component</h3>
*
* <p>Place the {@code Select} annotation on each record component of the
* Java record that is used as the result type of the repository method.
* Assign the annotation value to be the name of an entity attribute that
* has the same type as the record component.</p>
*
* <pre>
* &#64;Repository
* public interface Cars extends BasicRepository&lt;Car, String&gt; {
* record ModelInfo(&#64;Select(_Car.MODEL) String model,
* &#64;Select(_Car.MAKE) String manufacturer,
* &#64;Select(_Car.YEAR) int designYear) {}
*
* &#64;Find
* Optional&lt;ModelInfo&gt; getModelInfo(@By(_Car.VIN) String vehicleIdNum);
* }
* </pre>
*
* <p>For more concise code, the {@code Select} annotation can be omitted from
* record components that have the same name as the entity attribute name,
* such as {@code model} in the above example.</p>
*
* <p>The examples above use the
* {@linkplain jakarta.data/jakarta.data.metamodel static metamodel},
* to avoid hard coding String values for the entity attribute names.</p>
*/
String[] value();
}
47 changes: 46 additions & 1 deletion api/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022,2024 Contributors to the Eclipse Foundation
* Copyright (c) 2022,2025 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.
Expand Down Expand Up @@ -33,6 +33,7 @@
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
import jakarta.data.repository.Save;
import jakarta.data.repository.Select;
import jakarta.data.repository.Update;

import java.util.Set;
Expand Down Expand Up @@ -843,6 +844,48 @@
* Sort.asc("name"));
* </pre>
*
* <h2>Returning subsets of entity attributes</h2>
*
* <p>In addition to retrieving results that are entities, repository find methds
* can be written to retrieve single entity attribute results, as well as
* multiple entity attribute results (represented as a Java record).</p>
*
* <h3>Single entity attribute result type</h3>
*
* <p>The {@link Select} annotation chooses the entity attribute.
* The result type within the repository method return type must be consistent
* with the entity attribute type. For example, if a {@code Person} entity
* has attributes including {@code year}, {@code month}, {@code day}, and
* {@code precipitation}, of which the latter is of type {@code float},</p>
*
* <pre>
* &#64;Find(Weather.class)
* &#64;Select("precipitation")
* &#64;OrderBy("precipitation")
* List&lt;Float&gt; precipitationIn(&#64;By("month") Month monthOfYear,
* &#64;By("year") int year);
* </pre>
*
* <h3>Multiple entity attributes result type</h3>
*
* <p>The repository method return type includes a Java record to represent a
* subset of entity attributes. If the record component names do not match the
* entity attribute names, use the {@link Select} annotation to indicate the
* entity attribute name. For example, if a {@code Person} entity has attributes
* {@code ssn}, {@code firstName}, {@code middleName}, and {@code lastName},</p>
*
* <pre>
* public record Name(String firstName,
* String middleName,
* &#64;Select("lastName") String surname) {}
*
* &#64;Find(Person.class)
* Optional&lt;Name&gt; getName(&#64;By("ssn") long socialSecurityNum);
* </pre>
*
* <p>The entity class value that is supplied to the {@link Find} annotation can
* be omitted if it is the same as the primary entity type of the repository.</p>
*
* <h2>Repository default methods</h2>
*
* <p>A repository interface may declare any number of {@code default} methods
Expand Down Expand Up @@ -938,6 +981,8 @@
* as an entity, such as {@code MyEntity}, {@code MyEntity[]}, or
* {@code List<MyEntity>}, the entity type is determined by the method
* parameter type.</li>
* <li>For repository methods annotated with {@link Find} where the
* {@link Find#value value} is a valid entity class.</li>
* <li>For {@code find} and {@code delete} methods where the return type is
* a type, an array of a type, or is parameterized with a type annotated
* as an entity, such as {@code MyEntity}, {@code MyEntity[]}, or
Expand Down
2 changes: 1 addition & 1 deletion spec/src/main/asciidoc/repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ A superinterface of a repository interface must either:

A Jakarta Data implementation must treat abstract methods inherited by a repository interface as if they were directly declared by the repository interface.

Repositories perform operations on entities. For repository methods that are annotated with `@Insert`, `@Update`, `@Save`, or `@Delete`, the entity type is determined from the method parameter type. For `find` and `delete` methods where the return type is an entity, array of entity, or parameterized type such as `List<MyEntity>` or `Page<MyEntity>`, the entity type is determined from the method return type. For `count`, `exists`, and other `find` and `delete` methods that do not return the entity or accept the entity as a parameter, the entity type cannot be determined from the method signature and a _primary entity type_ must be defined for the repository.
Repositories perform operations on entities. For repository methods that are annotated with `@Insert`, `@Update`, `@Save`, or `@Delete`, the entity type is determined from the method parameter type. For repository methods that are annotated with a `@Find`, the entity type is determined from the annotation value, if a valid entity type. Otherwise, for `find` and `delete` methods where the return type is an entity, array of entity, or parameterized type such as `List<MyEntity>` or `Page<MyEntity>`, the entity type is determined from the method return type. For `count`, `exists`, and other `find` and `delete` methods that do not return the entity or accept the entity as a parameter, the entity type cannot be determined from the method signature and a _primary entity type_ must be defined for the repository.

Users of Jakarta Data declare a primary entity type for a repository by inheriting from a built-in repository super interface, such as `BasicRepository`, and specifying the primary entity type as the first type variable. For repositories that do not inherit from a super interface with a type parameter to indicate the primary entity type, lifecycle methods on the repository determine the primary entity type. To do so, all lifecycle methods where the method parameter is a type, an array of type, or is parameterized with a type that is annotated as an entity, must correspond to the same entity type. The primary entity type is assumed for methods that do not otherwise specify an entity type, such as `countByPriceLessThan`. Methods that require a primary entity type raise `MappingException` if a primary entity type is not provided.

Expand Down

0 comments on commit 4887c0d

Please sign in to comment.