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 property. For example,
@Entity public class Product { @Id public long id; public String name; public float price; public int yearProduced; ... }
The application defines interface methods on separate classes called repositories
to perform queries and other operations on entities. Repositories are interface classes
that are annotated with the Repository
annotation. For example,
@Repository public interface Products extends BasicRepository<Product, Long> { @Insert void create(Product prod); @OrderBy("price") List<Product> findByNameIgnoreCaseLikeAndPriceLessThan(String namePattern, float max); @Query("UPDATE Product o SET o.price = o.price * (1.0 - ?1) WHERE o.yearProduced <= ?2") int discountOldInventory(float rateOfDiscount, int maxYear); ... }
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); found = products.findByNameIgnoreCaseLikeAndPriceLessThan("%cell%phone%", 900.0f); numDiscounted = products.discountOldInventory(0.15f, Year.now().getValue() - 1);
Jakarta Persistence and Jakarta NoSQL define entity models that you
may use in Jakarta Data. You may use jakarta.persistence.Entity
and the corresponding entity-related annotations of the Jakarta Persistence
specification to define entities for relational databases.
You may use jakarta.nosql.mapping.Entity
and the corresponding
entity-related annotations of the Jakarta NoSQL specification to define
entities for NoSQL databases. For other types of data stores, you may use
other entity models that are determined by the Jakarta Data provider
for the respective data store type.
Methods of repository interfaces must be styled according to a 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 that are placed upon the method and its parameters.
Built-in repository super interfaces, such as DataRepository
,
are provided as a convenient way to inherit commonly used methods and are
parameterized with the entity type and id type. Other built-in repository
interfaces, such as BasicRepository
, can be used in place of
DataRepository
and provide a base set of predefined repository methods
which serve as an optional starting point.
You can extend these built-in interfaces to add your own custom methods.
You can also define your own repository interface without inheriting from the
built-in super interfaces. You can copy individual method signatures from the
built-in repository methods onto your own, which is possible
because the built-in repository methods are consistent with the
same set of conventions that you use to write custom repository methods.
Entity property names are computed from the fields and accessor methods
of the entity class and must be unique ignoring case. For simple entity
properties, the field or accessor method name is used as the entity property
name. In the case of embedded classes within entities, entity property names
are computed by concatenating the field or accessor method names at each level,
delimited by _
or undelimited for query by method name (such as
findByAddress_ZipCode
or findByAddressZipCode
)
when referred to within repository method names, and delimited by
.
when used within annotation values, such as for
OrderBy.value()
and Query.value()
,
@Entity public class Order { @Id public String orderId; @Embedded public Address address; ... } @Embeddable public class Address { public int zipCode; ... } @Repository public interface Orders { @OrderBy("address.zipCode") List<Order> findByAddressZipCodeIn(List<Integer> zipCodes); @Query("SELECT o FROM Order o WHERE o.address.zipCode=?1") List<Order> forZipCode(int zipCode); @Save Order checkout(Order order); }
When using the Query by Method Name pattern
as well as the Parameter-based Conditions pattern,
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.
Methods with Entity Parameters
You can annotate a method with Insert
, Update
, Save
,
or Delete
if the method accepts a single parameter, which must be one of:
- An entity.
- An array of entity (variable arguments array is permitted).
- An
Iterable
of entity (subclasses such asList
are permitted).
Annotation | Description | Example |
Delete |
deletes entities | @Delete public void remove(person); |
Insert |
creates new entities | @Insert public List<Employee> add(List<Employee> newEmployees); |
Save |
update if exists, otherwise insert | @Save Product[] saveAll(Product... products) |
Update |
updates an existing entity | @Update public boolean modify(Product modifiedProduct); |
Refer to the JavaDoc of each annotation for more information.
Query by Method Name
Repository methods following the Query by Method Name pattern
must include the By
keyword in the method name and must not include
any life cycle annotations on the method or any data access related annotations
on the method parameters. Query conditions
are determined by the portion of the method name following the By
keyword.
Prefix | Description | Example |
countBy |
counts the number of entities | countByAgeGreaterThanEqual(ageLimit) |
deleteBy |
for delete operations | deleteByStatus("DISCONTINUED") |
existsBy |
for determining existence | existsByYearHiredAndWageLessThan(2022, 60000) |
find...By |
for find operations | findByHeightBetween(minHeight, maxHeight) |
updateBy |
for simple update operations | updateByIdSetModifiedOnAddPrice(productId, now, 10.0) |
When using the Query By Method Name pattern
the conditions are defined by the portion of the repository method name
(referred to as the Predicate) that follows the By
keyword,
in the same order specified.
Most conditions, such as Like
or LessThan
,
correspond to a single method parameter. The exception to this rule is
Between
, which corresponds to two method parameters.
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 |
numeric, strings, time | Requires that the entity's attribute value be within the range specified by two parameters. The minimum is listed first, then the maximum. | findByAgeBetween(minAge, maxAge) |
Key-value Wide-Column |
Contains |
collections, strings | For Collection attributes, requires that the entity's attribute value, which is a collection, includes the parameter value. For String attributes, requires that any substring of the entity's attribute value match the entity's attribute value, which can be a pattern with wildcard characters. | findByRecipientsContains(email)
findByDescriptionNotContains("refurbished") |
Key-value Wide-Column Document |
Empty |
collections | Requires that the entity's attribute is an empty collection or has a null value. | countByPhoneNumbersEmpty()
findByInviteesNotEmpty() |
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 |
numeric, strings, time | Requires that the entity's attribute value be larger than the parameter value. | findByStartTimeGreaterThan(startedAfter) |
Key-value Wide-Column |
GreaterThanEqual |
numeric, strings, time | 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 property name. |
countByStatusIgnoreCaseNotLike("%Delivered%")
findByZipcodeOrderByStreetIgnoreCaseAscHouseNumAsc(55904) |
Key-value Wide-Column Document Graph |
In |
all attribute types | Requires that the entity's attribute value be within the list that is the parameter value. | findByNameIn(names) |
Key-value Wide-Column Document Graph |
LessThan |
numeric, strings, time | Requires that the entity's attribute value be less than the parameter value. | findByStartTimeLessThan(startedBefore) |
Key-value Wide-Column |
LessThanEqual |
numeric, strings, time | 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
,
Rounded
, RoundedDown
, RoundedUp
, Trimmed
,
WithDay
, WithHour
, WithMinute
, WithMonth
,
WithQuarter
, WithSecond
, WithWeek
, WithYear
.
Reserved for find...By and count...By: 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 Repository Methods
The following is a table of valid return types.
The Method column shows name patterns for Query by Method Name.
For methods with the Query
annotation or Parameter-based Conditions,
which have flexible naming, refer to the equivalent Query by Method Name
pattern in the table. For example, to identify the valid return types for a method,
findNamed(String name, Pageable pagination)
, refer to the row for
find...By...(..., Pageable)
.
Method | Return Types | Notes |
countBy... |
long , Long ,
int , Integer ,
short , Short ,
Number |
Jakarta Persistence providers limit the maximum to Integer.MAX_VALUE |
deleteBy... ,
updateBy... |
void , Void ,
boolean , Boolean ,
long , Long ,
int , Integer ,
short , Short ,
Number |
Jakarta Persistence providers limit the maximum to Integer.MAX_VALUE |
existsBy... |
boolean , Boolean |
For determining existence. |
find...By... |
E ,
Optional<E> |
For queries returning a single item (or none) |
find...By... |
E[] ,
Iterable<E> ,
Streamable<E> ,
Collection<E> |
For queries where it is possible to return more than 1 item. |
find...By... |
Stream<E> |
The caller must arrange to close
all streams that it obtains from repository methods. |
find...By... |
Collection subtypes |
The subtype must have a public default constructor and support addAll or add |
find...By...(..., Pageable) |
Page<E> , KeysetAwarePage<E> ,
Slice<E> , KeysetAwareSlice<E> |
For use with pagination |
find...By... |
LinkedHashMap<K, E> |
Ordered map of Id attribute value to entity |
Return Types for Annotated Methods
Return types that are valid when using Query
depend on the Query Language
operation that is performed. For queries that correspond to operations in the above
table, the same return types must be supported as for the Query-by-Method-Name and
Parameter-based Conditions patterns.
Refer to the Insert
, Update
, Save
, and Delete
JavaDoc for valid return types when using those annotations. Whenever the
return type is an Iterable
subtype that is a concrete class,
the class must have a public default constructor and support
addAll
or add
.
Parameter-based Conditions
When using the Parameter-based Conditions pattern,
the method name must not include the By
keyword or at least one
of the parameters must be annotated with a data access related annotation.
The query conditions are defined by the method parameters.
You can annotate method parameters with the By
annotation
to specify the name of the entity attribute that the parameter value
is to be compared with. Otherwise, the method parameter name
must match the name of an entity attribute and you must compile
with the -parameters
compiler option that makes parameter
names available at run time.
The _
character can be used in method parameter names to
reference embedded attributes. All conditions are considered to be
the equality condition. All conditions must match in order to
retrieve an entity.
The following examples illustrate the difference between Query By Method Name and Parameter-based Conditions patterns. Both methods accept the same parameters and have the same behavior.
// Query by Method Name: Vehicle[] findByMakeAndModelAndYear(String makerName, String model, int year, Sort... sorts); // Parameter-based Conditions: Vehicle[] find(String make, String model, int year, Sort... sorts);
Additional Method Parameters
When using @Query
or the
Query By Method Name pattern or the
Parameter-based Find pattern,
after conditions are determined from the corresponding parameters,
the remaining repository method parameters are used to enable other
capabilities such as pagination, limits, and sorting.
Limits
You can cap the number of results that can be returned by a single
invocation of a repository find method by adding a Limit
parameter.
You can also limit the results to a positional range. For example,
@Query("SELECT o FROM Products o WHERE (o.fullPrice - o.salePrice) / o.fullPrice >= ?1 ORDER BY o.salePrice DESC") Product[] highlyDiscounted(float minPercentOff, Limit limit); ... first50 = products.highlyDiscounted(0.30, Limit.of(50)); ... second50 = products.highlyDiscounted(0.30, Limit.range(51, 100));
Pagination
You can request that results be paginated by adding a Pageable
parameter to a repository find method. For example,
Product[] findByNameLikeOrderByAmountSoldDescNameAsc( String pattern, Pageable pagination); ... page1 = products.findByNameLikeOrderByAmountSoldDescNameAsc( "%phone%", Pageable.ofSize(20));
Sorting at Runtime
When using pagination, you can dynamically supply sorting criteria
via the Pageable.sortBy(Sort...)
and Pageable.sortBy(Iterable)
methods. For example,
Product[] findByNameLike(String pattern, Pageable pagination); ... Pageable pagination = Pageable.ofSize(25).sortBy( Sort.desc("price"), Sort.asc("name")); page1 = products.findByNameLikeAndPriceBetween( namePattern, minPrice, maxPrice, pagination);
To supply sorting criteria dynamically without using pagination,
add one or more Sort
parameters (or Sort...
)
to a repository find method. For example,
Product[] findByNameLike(String pattern, Limit max, Sort... sortBy); ... page1 = products.findByNameLike(namePattern, Limit.of(25), Sort.desc("price"), Sort.desc("amountSold"), Sort.asc("name"));
Repository Default Methods
You can compose default methods on your repository interface to supply user-defined implementation.
Resource Accessor Methods
For some advanced scenarios, you might need access to an
underlying resource from the Jakarta Data provider, such as a
jakarta.persistence.EntityManager
,
javax.sql.DataSource
, or
java.sql.Connection
To obtain the above, you can define accessor methods on your repository interface, where the method has no parameters and its result value is one of the aforementioned types. When you invoke the method, 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 you invoke the
accessor method from a repository default method, the Jakarta Data provider
automatically closes the resource after the default method ends.
If you invoke the accessor method from outside the scope of a default method,
you are responsible for closing the resource instance.
Precedence of Repository Methods
The following precedence, from highest to lowest, is used when interpreting the meaning of repository methods.
- If you define a method as a Java default method and provide implementation, then your provided implementation is used.
- If you define a method with a Resource Accessor Method return type, then the method is implemented as a Resource Accessor Method.
- If you annotate a method with
Query
, then the method is implemented to run the corresponding Query Language query. - If you annotate a method with an annotation that defines the type of operation
(
Insert
,Update
,Save
, orDelete
), then the annotation determines how the method is implemented. - If you annotate any of the method parameters with a data access related annotation, then Parameter-based Conditions determine the implementation of the method.
- If you define a method according to the Query by Method Name pattern naming conventions, then the implementation follows the Query by Method Name pattern.
- Otherwise, Parameter-based Conditions determine the implementation of the method.
You must compile with the
-parameters
compiler option that makes parameter names available at run time.
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.
- For repository methods that are annotated with
Insert
,Update
,Save
, orDelete
where the method parameter is a type, an array of type, or is parameterized with a type that is annotated as an entity, the entity type is determined from the method parameter type. - For find and delete methods where the return type is a type, an array of type,
or is parameterized with a type that is annotated as an entity,
such as
MyEntity
,MyEntity[]
, orPage<MyEntity>
, the entity type is determined from the method return type.
The following precedence, from highest to lowest, is used to determine a primary entity type for a repository.
- You can explicitly define the primary entity type for a repository interface
by having the repository interface inherit from a super interface such as
CrudRepository
where the primary entity type is the type of the super interface's first type parameter. For example,Product
, in,@Repository public interface Products extends CrudRepository<Product, Long> { // applies to the primary entity type: Product int countByPriceLessThan(float max); }
- Otherwise, if you define life cycle methods (
Insert
,Update
,Save
, orDelete
) where the method parameter is a type, an array of type, or is parameterized with a type that is 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 Persistence
Persistence Context
When the Jakarta Data provider is backed by a Jakarta Persistence provider,
repository operations must behave as though backed by a stateless Entity Manager
in that persistence context is not preserved across the end of repository methods.
If you retrieve an entity via a repository and then modify the entity,
the modifications are not persisted to the database unless you explicitly invoke
a Save
or Update
operation in order to persist it.
Here is an example with BasicRepository.findById(K)
and
BasicRepository.save(S)
operations:
product = products.findById(prodNum).orElseThrow(); product.price = produce.price + 0.50; product = products.save(product);
Jakarta Transactions
Repository methods can participate in global transactions.
If a global transaction is active on the thread where a repository method runs
and the data source that backs the repository is capable of transaction enlistment,
then the repository operation runs as part of the transaction.
The repository operation does not commit or roll back a transaction
that was already present on the thread, but it might mark the transaction
for rollback only (jakarta.transaction.Status.STATUS_MARKED_ROLLBACK
)
if the repository operation fails.
When running in an environment where Jakarta Transactions and Jakarta CDI are
available, you can annotate repository methods with jakarta.transaction.Transactional
to define how the container manages transactions with respect to the repository
method.
Interceptor Annotations on Repository Methods
Interceptor bindings such as jakarta.transaction.Transactional
can annotate a
repository method. The repository bean honors these annotations when running in an
environment where the Jakarta EE technology that provides the interceptor is available.
-
Packages
PackageExported To ModulesOpened To ModulesDescriptionAll ModulesNoneJakarta Data provides an API that simplifies data access.All ModulesNoneCommon data access exceptions.All ModulesNoneSplits query results into slices or pages.All ModulesAll ModulesA repository is an interface annotated withRepository
that defines operations on entities.