Module jakarta.data
Jakarta Data standardizes a programming model where data is represented by simple Java classes and where operations on data are represented by interface methods.
The application defines simple Java objects called entities to represent data in the database. Fields or accessor methods designate each entity attribute. For example,
@Entity
public class Product {
@Id
public long id;
public String name;
public float price;
public LocalDate producedOn;
...
}
A repository is an interface annotated with the Repository annotation.
A repository declares methods which perform queries and other operations on entities.
For example,
@Repository
public interface Products extends BasicRepository<Product, Long> {
@Insert
void create(Product prod);
@OrderBy("price")
List<Product> findByNameIgnoreCaseLikeAndPriceLessThan(String namePattern, float max);
@Find
List<Product> search(
@By(_Product.NAME) @Is(Like.class) String namePattern,
Restriction<Product> restriction,
Order<Product> sortBy);
@Query("""
UPDATE Product SET price = price * (1.0 - ?1)
WHERE producedOn <= ?2
""")
int discountOldInventory(float rateOfDiscount, LocalDate untilDate);
...
}
Repository interfaces are implemented by the container/runtime and are made
available to applications via the jakarta.inject.Inject annotation. For
example,
@Inject
Products products;
...
products.create(newProduct);
phones = products.findByNameIgnoreCaseLikeAndPriceLessThan("%cell%phone%", 900.0f);
chargers = products.search("%charger%",
Restrict.all(_Product.description.contains("USB-C"),
_Product.price.lessThan(30.0f)),
Order.by(_Product.price.desc(),
_Product.id.asc()));
numDiscounted = products.discountOldInventory(0.15f,
LocalDate.now().minusYears(1));
Jakarta Persistence and Jakarta NoSQL define programming models for entity classes that may be used with Jakarta Data:
jakarta.persistence.Entityand the corresponding entity-related annotations of the Jakarta Persistence specification may be used to define entities stored in a relational database, orjakarta.nosql.Entityand the corresponding entity-related annotations of the Jakarta NoSQL specification may be used to define entities stored in a NoSQL database.
A Jakarta Data provider may define its own programming model for entity classes representing some other arbitrary kind of data.
Methods of repository interfaces must be styled according to a well-defined set of conventions, which instruct the container/runtime about the desired data access operation to perform. These conventions consist of patterns of reserved keywords within the method name, method parameters with special meaning, method return types, and annotations placed upon the method and its parameters.
Built-in repository superinterfaces, such as DataRepository,
are provided as a convenient way to inherit commonly used methods and
are parameterized by the entity type and by its id type. Other built-in
repository interfaces, such as BasicRepository, may be used in
place of DataRepository and provide a base set of predefined
repository operations serving as an optional starting point. The Java
application programmer may extend these built-in interfaces, adding
custom methods. Alternatively, the programmer may define a repository
interface without inheriting the built-in superinterfaces. A programmer
may even copy individual method signatures from the built-in repositories
to a repository which does not inherit any built-in superinterface. This
is possible because the methods of the built-in repository superinterfaces
respect the conventions defined for custom repository methods.
The following example shows an entity class, an embeddable class, and a repository interface:
@Entity
public class Purchase {
@Id
public String purchaseId;
@Embedded
public Address address;
...
}
@Embeddable
public class Address {
public int zipCode;
...
}
@Repository
public interface Purchases {
@Find
@OrderBy("address.zipCode")
List<Purchase> forZipCodes(
@By("address.zipCode") @Is(In.class) List<Integer> zipCodes);
@Query("WHERE address.zipCode = ?1")
List<Purchase> forZipCode(int zipCode);
@Save
Purchase checkout(Purchase purchase);
}
Entities
An entity programming model typically specifies an entity-defining
annotation that is used to identify entity classes. For Jakarta Persistence,
this is jakarta.persistence.Entity. For Jakarta NoSQL, it is
jakarta.nosql.Entity. A provider may even have no entity-defining
annotation and feature a programming model for entity classes where the
entity classes are unannotated.
Furthermore, an entity programming model must define an annotation which
identifies the attribute holding the unique identifier of an entity.
For Jakarta Persistence, it is jakarta.persistence.Id or
jakarta.persistence.EmbeddedId. For Jakarta NoSQL, it is
jakarta.nosql.Id. Alternatively, an entity programming model might
allow the identifier attribute to be identified via some convention.
Every entity has a unique identifier.
An entity has an arbitrary number of attributes.
Entity attribute names
Each attribute of an entity or embeddable class is assigned a name:
- when direct field access is used, the name of an entity attribute is simply the name of the Java field, but
- when property-based access is used, the name of the entity attribute is derived from the accessor methods, according to JavaBeans conventions.
Within a given entity class or embeddable class, names assigned to entity attributes must be unique ignoring case.
Furthermore, within the context of a given entity, each attribute of an embeddable class reachable by navigation from the entity class may be assigned a compound name. The compound name is obtained by concatenating the names assigned to each attribute traversed by navigation from the entity class to the attribute of the embedded class, optionally joined by a delimiter.
- For parameters of a
Findmethod, the delimiter is_. - For path expressions within a query, the delimiter
is
.. - For method names in Query by Method Name, the delimiter is
_and it is optional. For example,findByAddress_ZipCodeorfindByAddressZipCodeare both legal. - For arguments to constructor methods of
Sort, the delimiter is_or.. - For the
valuemember of theOrderByorByannotation the delimiter is_or..
A entity attribute name used in a Query by Method Name must not contain a keyword reserved by Query by Method Name.
Entity attribute types (basic types)
The following is a list of valid basic entity attribute types. These can be used as the types of repository method parameters for the respective entity attribute.
| Category | Basic Types | Notes |
| Primitives and primitive wrappers | boolean and Boolean
byte and Byte
char and Character
double and Double
float and Float
int and Integer
long and Long
short and Short |
Boolean values are sorted such that false < true. |
| Binary data | byte[] |
Not sortable. |
| Enumerated types | enum types |
It is provider-specific whether sorting is based on
Enum.ordinal() or Enum.name().
The Jakarta Persistence default of ordinal can be
overridden with the jakarta.persistence.Enumerated
annotation. |
| Large numbers | BigDecimal
BigInteger |
|
| Textual data | String |
|
| Time and Dates | Instant
LocalDate
LocalDateTime
LocalTime
Year |
|
| Universally unique identifier | UUID |
All of the basic types are sortable except for byte[].
A Jakarta Data provider might allow additional entity attribute types.
Lifecycle methods
A lifecycle method makes changes to persistent data in the data store.
A lifecycle method must be annotated with a lifecycle annotation such as
Insert, Update, Save, or Delete. The
method must accept a single parameter, whose type is either:
- the class of the entity, or
List<E>orE[]whereEis the class of the entities.
The annotated method must be declared void, or, except in the
case of @Delete, have a return type that is the same as the type
of its parameter.
| Annotation | Description | Example |
Delete |
deletes entities | @Deletepublic void remove(person); |
Insert |
creates new entities | @Insertpublic List<Employee> add(List<Employee> newEmployees); |
Save |
update if exists, otherwise insert | @SaveProduct[] saveAll(Product... products) |
Update |
updates an existing entity | @Updatepublic boolean modify(Product modifiedProduct); |
Refer to the API documentation for Insert, Update, Delete,
and Save for further information about these annotations.
Parameter-based Find and Delete methods
The Find annotation always indicates a parameter-based automatic
query method. The Delete annotation indicates a parameter-based
automatic query method when the method has no entity type parameters.
The method parameters determine the query conditions. The method name does
not determine the semantics of the method.
Method parameters
Each parameter of the annotated method must fit into one of the following categories:
- The parameter has exactly the same type and name as an attribute of the
entity class. The
@Byannotation assigns the name. The@Isannotation, which is optional, supplies a subtype ofConstraintthat indicates the comparison. If theByannotation is missing, the method parameter name must match the name of an entity attribute and the repository must be compiled with the-parameterscompiler option so that parameter names are available at runtime. For example,@Find Optional<Product> get(@By("id") long productId); @Find @OrderBy("price") List<Product> discounted( @By("discount") @Is(AtLeast.class) float minAmount); @Find @OrderBy("price") Product[] named(String name); - The parameter is a
Constraintor a subtype ofConstraintand has exactly the same name (per the rule from category 1) as an attribute of the entity class. The constraint must be parameterized with the same type (or if primitive, the corresponding wrapper type) as the attribute. For example,@Delete int discontinue(@By("id") In<Long> productIds); @Find @OrderBy("price") @OrderBy("name") Stream<Product> pricedUnder(LessThan<Float> price); - The parameter is a
Restrictionand its type parameter is the entity class. For example,@Find List<Product> search(Restriction<Product> restrict); - The parameter is a
Limit,Sort,Order, orPageRequest(discussed under the section titled Special parameters) and the repository method is annotated withFind. For example,@Find Page<Product> getPage(PageRequest pageRequest, Order<Product> sortBy);
Comparisons
Categories 1 and 2 above identify the name of an entity attribute against
which a supplied value is compared. A subtype of Constraint is used
as the parameter type or supplied by the Is annotation to indicate
the type of comparison. The equality comparison is used in the absence of a
Constraint subtype when no comparison is indicated.
For a repository method, all entity attribute constraints (defined by categories 1 and 2) and restrictions (category 3) must be satisfied for a record to satisfy the query.
Method parameters for embedded attributes
The . character may be used in the By annotation value
to reference an embedded attribute.
@Find
Stream<Person> livingInZipCode(@By("address.zipCode") int zip);
The _ character may be used in a method parameter name to
reference an embedded attribute.
@Find Stream<Person> livingInCity(String address_city);
JDQL query methods
The Query annotation specifies that a method executes a query written
in Jakarta Data Query Language (JDQL) or Jakarta Persistence Query Language (JPQL).
A Jakarta Data provider is not required to support the complete JPQL language,
which targets relational data stores.
Each parameter of the annotated method must either:
- have exactly the same name (the parameter name in the Java source, or a name
assigned by
@Param) and type as a named parameter of the query, - have exactly the same type and position within the parameter list of the method as a positional parameter of the query, or
- be of type
Limit,Order,PageRequest, orSort.
The Param annotation associates a method parameter with a named parameter.
The Param annotation is unnecessary when the method parameter name matches the
name of a named parameter and the application is compiled with the -parameters
compiler option making parameter names available at runtime.
// example using named parameters
@Query("where age between :min and :max order by age")
List<Person> peopleInAgeRange(int min, int max);
// example using an ordinal parameter
@Query("where ssn = ?1 and deceased = false")
Optional<Person> person(String ssn);
Refer to the API documentation for @Query for further
information.
Query by Method Name
The Query by Method Name pattern translates the name of the
repository method into a query. The repository method must not include the
@Find annotation, @Query annotation, or any life cycle
annotations on the method, and it must not include any data access related
annotations on the method parameters. The method name must be composed of
one or more clauses that specify the query's action and optionally any
restrictions for matching and ordering of results. The following table lists
the method name prefixes available and shows an example query for each.
| Prefix | Description | Example |
count |
counts the number of entities | countByAgeGreaterThanEqual(ageLimit) |
delete |
for delete operations | deleteByStatus("DISCONTINUED") |
exists |
for determining existence | existsByYearHiredAndWageLessThan(2022, 60000) |
find |
for find operations | findByHeightBetweenOrderByAge(minHeight, maxHeight) |
The method name must begin with an action clause that starts with a
prefix (count, delete, exists, or find) that
specifies the action of the query. The find prefix can optionally be
followed by the keyword First and 0 or more digits, which limit the
number of results retrieved. The remaining portion of the action clause is
ignored text that must not contain reserved keywords.
A restriction clause consisting of the By keyword
followed by one or more conditions can optionally follow the action clause.
Multiple conditions must be delimited by the And or Or keyword.
Each condition consists of a case insensitive entity attribute name, optionally
followed by the IgnoreCase keyword (for text-typed attributes), optionally
followed by the Not keyword, optionally followed by a condition operator
keyword such as StartsWith. The equality condition is implied when no
condition operator keyword is present. Most of the condition operations, such as
Like or LessThan, correspond to a single method parameter. The
exceptions to this rule are Between, which corresponds to two method
parameters, and Null, True, and False, which require
no method parameters.
A find query can optionally end with an order, consisting of
the OrderBy keyword and one or more pairings of case-insensitive entity
attribute name followed by the Asc or Desc keyword, indicating
ascending or descending sort. In the case of a single entity attribute, the
paired keyword can be omitted, in which case ascending sort is implied.
Key-value and Wide-Column databases raise UnsupportedOperationException
for queries on attributes other than the identifier/key.
Reserved keywords for Query by Method Name
| Keyword | Applies to | Description | Example | Unavailable In |
And |
conditions | Requires both conditions to be satisfied in order to match an entity. | findByNameLikeAndPriceLessThanEqual(namePattern, maxPrice) |
Key-value Wide-Column |
Between |
sortable basic types | Requires that the entity's attribute value be within the range specified by two parameters, inclusive of the parameters. The minimum is listed first, then the maximum. | findByAgeBetween(minAge, maxAge) |
Key-value Wide-Column |
Contains |
strings | Requires that a substring of the entity's attribute value matches the parameter value, which can be a pattern. | findByNameContains(middleName) |
Key-value Wide-Column Document Graph |
EndsWith |
strings | Requires that the characters at the end of the entity's attribute value match the parameter value, which can be a pattern. | findByNameEndsWith(surname) |
Key-value Wide-Column Document Graph |
False |
boolean | Requires that the entity's attribute value has a boolean value of false. | findByCanceledFalse() |
Key-value Wide-Column |
GreaterThan |
sortable basic types | Requires that the entity's attribute value be larger than the parameter value. | findByStartTimeGreaterThan(startedAfter) |
Key-value Wide-Column |
GreaterThanEqual |
sortable basic types | Requires that the entity's attribute value be at least as big as the parameter value. | findByAgeGreaterThanEqual(minimumAge) |
Key-value Wide-Column |
IgnoreCase |
strings | Requires case insensitive comparison. For query conditions
as well as ordering, the IgnoreCase keyword can be
specified immediately following the entity attribute name. |
countByStatusIgnoreCaseNotLike("%Delivered%")
findByZipcodeOrderByStreetIgnoreCaseAscHouseNumAsc(55904) |
Key-value Wide-Column Document Graph |
In |
sortable basic types | Requires that the entity attribute value belong to the Set that is
the parameter value. |
findByMonthIn(Set.of(Month.MAY, Month.JUNE)) |
Key-value Wide-Column Document Graph |
LessThan |
sortable basic types | Requires that the entity's attribute value be less than the parameter value. | findByStartTimeLessThan(startedBefore) |
Key-value Wide-Column |
LessThanEqual |
sortable basic types | Requires that the entity's attribute value be at least as small as the parameter value. | findByAgeLessThanEqual(maximumAge) |
Key-value Wide-Column |
Like |
strings | Requires that the entity's attribute value match the parameter value, which can be a pattern. | findByNameLike(namePattern) |
Key-value Wide-Column Document Graph |
Not |
condition | Negates a condition. | deleteByNameNotLike(namePattern)
findByStatusNot("RUNNING") |
Key-value Wide-Column |
Null |
nullable types | Requires that the entity's attribute has a null value. | findByEndTimeNull()
findByAgeNotNull() |
Key-value Wide-Column Document Graph |
Or |
conditions | Requires at least one of the two conditions to be satisfied in order to match an entity. | findByPriceLessThanEqualOrDiscountGreaterThanEqual(maxPrice, minDiscount) |
Key-value Wide-Column |
StartsWith |
strings | Requires that the characters at the beginning of the entity's attribute value match the parameter value, which can be a pattern. | findByNameStartsWith(firstTwoLetters) |
Key-value Wide-Column Document Graph |
True |
boolean | Requires that the entity's attribute value has a boolean value of true. | findByAvailableTrue() |
Key-value Wide-Column |
| Keyword | Applies to | Description | Example | Unavailable In |
First |
find...By | Limits the amount of results that can be returned by the query
to the number that is specified after First,
or absent that to a single result. |
findFirst25ByYearHiredOrderBySalaryDesc(int yearHired)
findFirstByYearHiredOrderBySalaryDesc(int yearHired) |
Key-value Wide-Column Document Graph |
| Keyword | Description | Example |
Asc |
Specifies ascending sort order for findBy queries |
findByAgeOrderByFirstNameAsc(age) |
Desc |
Specifies descending sort order for findBy queries |
findByAuthorLastNameOrderByYearPublishedDesc(surname) |
OrderBy |
Sorts results of a findBy query according to one or more entity attributes.
Multiple attributes are delimited by Asc and Desc,
which indicate ascending and descending sort direction.
Precedence in sorting is determined by the order in which attributes are listed. |
findByStatusOrderByYearHiredDescLastNameAsc(empStatus) |
Key-value and Wide-Column databases raise UnsupportedOperationException
if an order clause is present.
Reserved for future use
The specification does not define behavior for the following keywords, but reserves them as keywords that must not be used as entity attribute names when using Query by Method Name. This gives the specification the flexibility to add them in future releases without introducing breaking changes to applications.
Reserved for query conditions: AbsoluteValue, CharCount,
ElementCount, Empty,
Rounded, RoundedDown, RoundedUp, Trimmed,
WithDay, WithHour, WithMinute, WithMonth,
WithQuarter, WithSecond, WithWeek, WithYear.
Reserved for find and count: Distinct.
Reserved for updates: Add, Divide, Multiply, Set, Subtract.
Wildcard characters
Wildcard characters for patterns are determined by the data access provider.
For Jakarta Persistence providers, _ matches any one character
and % matches 0 or more characters.
Logical operator precedence
For relational databases, the logical operator And
is evaluated on conditions before Or when both are specified
on the same method. Precedence for other database types is limited to
the capabilities of the database.
Return types for Query by Method Name
The following is a table of valid return types. The Method column shows name patterns for Query by Method Name.
| Method | Return Types | Notes |
count |
long |
|
delete |
void,
long,
int |
|
exists |
boolean |
For determining existence |
find |
E,
Optional<E> |
For queries returning a single item (or none) |
find |
E[],
List<E> |
For queries where it is possible to return more than 1 item |
find |
Stream<E> |
The caller must call close
for every stream returned by the repository method |
find accepting PageRequest |
Page<E>, CursoredPage<E> |
For use with pagination |
The following examples illustrate the difference between Query By Method Name and parameter-based automatic query methods. Both methods accept the same parameters and have the same behavior.
// Query by Method Name
Vehicle[] findFirst50ByMakeAndModelAndYearBetween(String makerName,
String model,
int minYear,
int maxYear,
Order<Vehicle> sorts);
// parameter-based conditions
@Find
@First(50)
Vehicle[] search(String make,
String model,
@By(_Vehicle.YEAR) @Is(AtLeast.class) int minYear,
@By(_Vehicle.YEAR) @Is(AtMost.class) int maxYear,
Order<Vehicle> sorts);
Special parameters
A repository method annotated @Query, @Find or
following the Query by Method Name pattern may have special
parameters of type Limit, Order, Sort, or
PageRequest if the method return type indicates that the method may
return multiple entities.
A repository method annotated @Query, @Find or
@Delete (except when defined as a lifecycle method) can have
a special parameter of type Restriction.
Special parameters occur after parameters related to query conditions and JDQL query parameters. Special parameters enable limits, pagination, sorting, and restrictions to be determined at runtime.
Limits
The number of results returned by a single invocation of a repository
find method may be limited by adding a parameter of type Limit.
The results may even be limited to a positioned range. For example,
@Query("WHERE (fullPrice - salePrice) / fullPrice >= ?1 ORDER BY salePrice DESC, id ASC")
Product[] highlyDiscounted(float minPercentOff, Limit limit);
...
first50 = products.highlyDiscounted(0.30, Limit.of(50));
...
second50 = products.highlyDiscounted(0.30, Limit.range(51, 100));
Pagination
A repository find method with a parameter of type PageRequest
allows its results to be split and retrieved in pages. For example,
Page<Product> findByNameLikeOrderByAmountSoldDescIdAsc(
String pattern, PageRequest pageRequest);
...
page1 = products.findByNameLikeOrderByAmountSoldDescIdAsc(
"%phone%", PageRequest.ofSize(20));
When using pagination, always ensure that the ordering is consistent across invocations. One way to achieve this is to include the unique identifier in the sort criteria.
Sorting
When a page is requested with a PageRequest, dynamic sorting
criteria may be supplied by passing instances of Sort or Order.
For example,
@Find
Page<Product> pricedWithin(@By("name") @Is(Like.class) String pattern,
@By("price") @Is(AtLeast.class) float minPrice,
@By("price") @Is(AtMost.class) float maxPrice,
PageRequest pageRequest,
Order<Product> order);
...
PageRequest page1Request = PageRequest.ofSize(25);
page1 = products.pricedWithin(
namePattern,
minPrice,
maxPrice,
page1Request,
Order.by(Sort.desc("price"),
Sort.asc("id")));
To supply sort criteria dynamically without using pagination, an
instance of Order may be populated with one or more instances
of Sort and passed to the repository find method. For example,
@Find
Product[] named(@By("name") @Is(Like.class) String pattern,
Limit max,
Order<Product> sortBy);
...
found = products.nameLiked(namePattern,
Limit.of(25),
Order.by(Sort.desc("price"),
Sort.desc("amountSold"),
Sort.asc("id")));
Generic, untyped Sort criteria can be supplied directly to a
repository method with a variable arguments Sort<?>... parameter.
For example,
@Find
Product[] namedLike(@By("name") @Is(Like.class) String pattern,
Limit max,
Sort<?>... sortBy);
...
found = products.namedLike(namePattern,
Limit.of(25),
Sort.desc("price"),
Sort.desc("amountSold"),
Sort.asc("name"));
Restrictions
Restrictions can be supplied at runtime to @Find and
@Delete and @Query methods that include a
parameter of type Restriction in their method signature. The
type parameter of the method parameter must be the entity class.
For example,
@Find
List<Product> namedLike(@By(_Product.NAME) Like pattern,
Restriction<Product> restrict,
Order<Product> sorts);
Static metamodel
The starting point for obtaining restrictions is a
StaticMetamodel class, which is typically generated by tooling to
have the same name as the entity class, except prefixed with _.
A static metamodel class, _Product, for the example
Product entity is shown in the jakarta.data.metamodel
package documentation.
Singular restrictions
Singular restrictions are obtained from a static metamdel class field
that has the same name as the entity attribute and whose type is an
Attribute subtype. The subtype, which is also a type of
Expression, exposes a method for each available restriction.
Some examples of singular restrictions are:
_Product.name.startsWith(prefix)
_Product.price.lessThanEqual(maxPrice)
_Product.producedOn.notNull()
The static metamodel expression/attribute also exposes methods that
obtain other Expressions, from which additional restrictions can
be formed. An example is following casse insensitive comparison that
utilizes the lower expression,
_Product.name.lower().startsWith(prefix.toLowerCase());
The following example obtains a singular restriction on the price of a
product being less than an amount and supplies this restriction to the
namedLike repository method from the beginning of this section on
Restrictions.
found = products.namedLike(
Like.pattern("%keyboard%"),
_Product.price.lessThan(100.0f),
Order.by(_Product.price.desc(),
_Product.name.asc()));
Composite restrictions
Composite restrictions combine multiple singular restrictions. They are
obtained from the Restrict API, which has methods to require that
all listed restrictions be met
or that at least any one of the
listed restrictions be met.
The following example obtains a composite restriction on the price of
a product (between $700 and $1500) and its production date (within the past
2 years). It supplies the composite restriction to the namedLike
repository method from the beginning of this section on
Restrictions.
found = products.namedLike(
Like.pattern("%computer%"),
Restrict.all(_Product.price.between(700.0f, 1500.0f),
_Product.producedOn.greaterThan(LocalDate.now().minusYears(2))),
Order.by(_Product.price.desc(),
_Product.id.asc()));
Maximum number of results
Apply the @First annotation to a repository Find or
Query method to establish a maximum number of results across all
usage of the method. The default value of 1 allows you to avoid the
NonUniqueResultException that would normally occurs when multiple
entities match a query that is performed by a repository method that has a
singular result type. For example,
@Find
@First
@OrderBy("hourlyWage")
Optional<Employee> withLowestWage(String jobRole);
Returning subsets of entity attributes
In addition to retrieving results that are entities, repository find methods can be written to retrieve single entity attribute results, as well as multiple entity attribute results (represented as a Java record).
Single entity attribute result type
For Find methods,
the 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 Weather entity
has attributes including year, month, day, and
precipitation, of which the latter is of type float,
@Find(Weather.class)
@Select("precipitation")
@OrderBy("precipitation")
List<Float> precipitationIn(@By("month") Month monthOfYear,
@By("year") int year);
For Query methods, the SELECT clause specifies a single
entity attribute. For example,
@Query("SELECT precipitation FROM Weather " +
" WHERE month=?1 AND year=?2" +
" ORDER BY precipitation ASC")
List<Float> precipitationIn(Month monthOfYear, int year);
Multiple entity attributes result type
For Find methods, a Java record return type represents a
subset of entity attributes. If the record component names do not match the
entity attribute names, use the Select annotation to indicate the
entity attribute name. For example, if a Person entity has attributes
ssn, firstName, middleName, and lastName,
public record Name(String firstName,
String middleName,
@Select("lastName") String surname) {}
@Find(Person.class)
Optional<Name> getName(@By("ssn") long socialSecurityNum);
The entity class value that is supplied to the Find annotation can
be omitted if it is the same as the primary entity type of the repository.
For Query methods, the SELECT clause lists the entity
attributes and the method returns a Java record, which must have a constructor
accepting the entity attributes in the order listed within the SELECT
clause. For example,
public record Name(String firstName,
String middleName,
String surname) {}
@Query("SELECT firstName, middleName, lastName FROM Person WHERE ssn=?1")
Optional<Name> getName(long socialSecurityNum);
If all record components have names that match the entity attributes or
map to a valid entity attribute name via the Select annotation,
then the SELECT clause can be omitted. For example,
public record Name(String firstName,
String middleName,
@Select("lastName") String surname) {}
@Query("FROM Person WHERE ssn=?1")
Optional<Name> getName(long socialSecurityNum);
Repository default methods
A repository interface may declare any number of default methods
with user-written implementations.
Resource accessor methods
In advanced scenarios, the application program might make direct use of
some underlying resource acquired by the Jakarta Data provider, such as a
javax.sql.DataSource, java.sql.Connection, or even an
jakarta.persistence.EntityManager.
To expose access to an instance of such a resource, the repository interface may declare an accessor method, a method with no parameters whose return type is the type of the resource, for example, one of the types listed above. When this method is called, the Jakarta Data provider supplies an instance of the requested type of resource.
For example,
@Repository
public interface Cars extends BasicRepository<Car, Long> {
...
EntityManager getEntityManager();
default Car[] advancedSearch(SearchOptions filter) {
EntityManager em = getEntityManager();
... use entity manager
return results;
}
}
If the resource type inherits from AutoCloseable and the
accessor method is called from within an invocation of a default method
of the repository, the Jakarta Data provider automatically closes the
resource after the invocation of the default method ends. On the other
hand, if the accessor method is called from outside the scope of a
default method of the repository, it is not automatically closed, and
the application programmer is responsible for closing the resource
instance.
Precedence of repository methods
The following order, with the lower number having higher precedence, is used to interpret the meaning of repository methods.
- If the method is a Java
defaultmethod, then the provided implementation is used. - If a method has a resource accessor method return type recognized by the Jakarta Data provider, then the method is implemented as a resource accessor method.
- If a method is annotated with a query annotation
recognized by the Jakarta Data provider, such as
Query, then the method is implemented to execute the query specified by the query annotation. - If the method is annotated with an automatic query annotation,
such as
Find, or with a lifecycle annotation declaring the type of operation, for example, withInsert,Update,Save, orDelete, and the provider recognizes the annotation, then the annotation determines how the method is implemented. - If a method is named according to the conventions of Query by Method Name, then the implementation follows the Query by Method Name pattern.
A repository method which does not fit any of the listed patterns
and is not handled as a vendor-specific extension must either cause
an error at build time or raise UnsupportedOperationException
at runtime.
Identifying the type of entity
Most repository methods perform operations related to a type of entity.
In some cases, the entity type is explicit within the signature of the
repository method, and in other cases, such as countBy... and
existsBy... the entity type cannot be determined from the method
signature and a primary entity type must be defined for the repository.
Methods where the entity type is explicitly specified
In the following cases, the entity type is determined by the signature of the repository method.
- For repository methods annotated with
Insert,Update,Save, orDeletewhere the method parameter type is a type, an array of a type, or is parameterized with a type annotated as an entity, such asMyEntity,MyEntity[], orList<MyEntity>, the entity type is determined by the method parameter type. - For repository methods annotated with
Findwhere thevalueis a valid entity class. - For
findanddeletemethods where the return type is a type, an array of a type, or is parameterized with a type annotated as an entity, such asMyEntity,MyEntity[], orPage<MyEntity>, the entity type is determined by the method return type.
Identifying a primary entity type:
The following precedence, from highest to lowest, is used to determine a primary entity type for a repository.
- The primary entity type for a repository interface may be specified
explicitly by having the repository interface inherit a superinterface
like
CrudRepository, where the primary entity type is the argument to the first type parameter of the superinterface. For example,Product, in,@Repository public interface Products extends CrudRepository<Product, Long> { // applies to the primary entity type: Product long countByPriceLessThan(float max); } - Otherwise, if the repository declares lifecycle methods—that is,
has methods annotated with a lifecycle annotation like
Insert,Update,Save, orDelete, where the method parameter type is a type, an array of a type, or is parameterized with a type annotated as an entity—and all of these methods share the same entity type, then the primary entity type for the repository is that entity type. For example,@Repository public interface Products { @Insert List<Product> add(List<Product> p); @Update Product modify(Product p); @Save Product[] save(Product... p); // applies to the primary entity type: Product boolean existsByName(String name); }
Jakarta Validation
When a Jakarta Validation provider is present, constraints that are defined on repository method parameters and return values are validated according to the section, "Method and constructor validation", of the Jakarta Validation specification.
The jakarta.validation.Valid annotation opts in to cascading validation,
causing constraints within the objects that are supplied as parameters
or returned as results to also be validated.
Repository methods raise jakarta.validation.ConstraintViolationException
if validation fails.
The following is an example of method validation, where the
parameter to findByEmailIn must not be the empty set,
and cascading validation, where the Email and NotNull constraints
on the entity that is supplied to save are validated,
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
...
@Repository
public interface AddressBook extends DataRepository<Contact, Long> {
List<Contact> findByEmailIn(@NotEmpty Set<String> emails);
@Save
void save(@Valid Contact c);
}
@Entity
public class Contact {
@Email
@NotNull
public String email;
@Id
public long id;
...
}
Jakarta Interceptors
A repository interface or method of a repository interface may be annotated with an
interceptor binding annotation. In the Jakarta EE environment, or in any other environment
where Jakarta Interceptors is available and integrated with Jakarta CDI, the repository
implementation is instantiated by the CDI bean container, and the interceptor binding type
is declared @Inherited, the interceptor binding annotation is inherited by the
repository implementation, and the interceptors bound to the annotation are applied
automatically by the implementation of Jakarta Interceptors.
Jakarta Transactions
When Jakarta Transactions is available, repository methods can participate in global transactions. If a global transaction is active on the thread of execution in which a repository method is called, and the data source backing the repository is capable of transaction enlistment, then the repository operation is performed within the context of the global transaction.
The repository operation must not commit or roll back a transaction which was
already associated with the thread in which the repository operation was called, but it
might cause the transaction to be marked for rollback if the repository operation fails,
that is, it may set the transaction status to Status.STATUS_MARKED_ROLLBACK.
A repository interface or method of a repository interface may be marked with the
annotation jakarta.transaction.Transactional. When a repository operation marked
@Transactional is called in an environment where both Jakarta Transactions and
Jakarta CDI are available, the semantics of this annotation are observed during execution
of the repository operation.
Jakarta Concurrency
When Jakarta Concurrency is available, an asynchronous repository method may be
annotated with jakarta.enterprise.concurrent.Asynchronous to cause the
method to run asynchronously to the method invoker, as outlined by the section
titled Asynchronous Methods in the Jakarta Concurrency specification.
The return type must be void or CompletionStage<R> where
R is a type that would be a valid return type for a non-asynchronous
repository method.
In the following example, the method setPriceAsync()
immediately returns a CompletionStage<Integer> to the caller.
After the operation is performed by the database, the CompletionStage completes
with a value of 1 or 0 depending on whether a matching record
is found in the database. If an error occurs, the CompletionStage
completes exceptionally
with the error.
import jakarta.data.*;
import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import java.util.concurrent.CompletionStage;
@Repository
public interface Products extends BasicRepository<Product, Long> {
@Asynchronous
@Transactional(TxType.REQUIRES_NEW)
@Query("UPDATE Product SET price=?1 WHERE id=?2")
CompletionStage<Integer> setPriceAsync(float newPrice, Long productId);
}
-
Packages
PackageExported To ModulesOpened To ModulesDescriptionAll ModulesNoneJakarta Data provides an API that simplifies data access.All ModulesNoneAll ModulesNoneAll ModulesNoneCommon data access exceptions.All ModulesNoneAll ModulesNoneA static metamodel for entities that are used in Jakarta Data repositories.All ModulesNoneAll ModulesNoneSplits query results into pages.All ModulesNoneAll ModulesAll ModulesA repository is an interface annotated withRepositorythat defines operations on entities.All ModulesNoneAll ModulesNoneAll ModulesNoneAll ModulesNoneAll ModulesNone