Specification: Jakarta Data

Version: 1.0.0-M2

Status: Draft

Release: December 04, 2023

Copyright (c) 2022, 2023 Eclipse Foundation.

Eclipse Foundation Specification License

By using and/or copying this document, or the Eclipse Foundation document from which this statement is linked, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions:

Permission to copy, and distribute the contents of this document, or the Eclipse Foundation document from which this statement is linked, in any medium for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the document, or portions thereof, that you use:

  • link or URL to the original Eclipse Foundation document.

  • All existing copyright notices, or if one does not exist, a notice (hypertext is preferred, but a textual representation is permitted) of the form: "Copyright (c) [$date-of-document] Eclipse Foundation, Inc. <<url to this license>>"

Inclusion of the full text of this NOTICE must be provided. We request that authorship attribution be provided in any software, documents, or other items or products that you create pursuant to the implementation of the contents of this document, or any portion thereof.

No right to create modifications or derivatives of Eclipse Foundation documents is granted pursuant to this license, except anyone may prepare and distribute derivative works and portions of this document in software that implements the specification, in supporting materials accompanying such software, and in documentation of such software, PROVIDED that all such works include the notice below. HOWEVER, the publication of derivative works of this document for use as a technical specification is expressly prohibited.

The notice is:

"Copyright (c) 2022, 2023 Eclipse Foundation. This software or document includes material copied from or derived from Jakarta Data and https://jakarta.ee/specifications/data/."

Disclaimers

THIS DOCUMENT IS PROVIDED "AS IS," AND THE COPYRIGHT HOLDERS AND THE ECLIPSE FOUNDATION MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE CONTENTS OF THE DOCUMENT ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.

THE COPYRIGHT HOLDERS AND THE ECLIPSE FOUNDATION WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE DOCUMENT OR THE PERFORMANCE OR IMPLEMENTATION OF THE CONTENTS THEREOF.

The name and trademarks of the copyright holders or the Eclipse Foundation may NOT be used in advertising or publicity pertaining to this document or its contents without specific, written prior permission. Title to copyright in this document will at all times remain with copyright holders.

Jakarta Data

1. Introduction

The Jakarta Data specification provides an API to simplify data access. It enables the Java developer to focus on the data model, while delegating away the complexities of data persistence. To make this possible, Jakarta Data includes a variety of features such as pre-built interfaces for data access, offset and cursor based pagination strategies, and the ability to compose custom query methods that the framework implements.

Data is a primary concern of most applications, and dealing with a database presents one of the most significant challenges within software architecture. Beyond selecting from the various database options available in the market, it is necessary to consider the intricacies of persistence integrations. Jakarta Data simplifies the lives of Java developers by providing a solution that streamlines data access and manipulation.

In this context, a domain-centric approach refers to designing the application’s architecture primarily focusing on the domain model. It means that the application’s data and logic structure and organization revolve around the core domain concepts and business rules, ensuring that the domain model plays a central role in shaping the application’s structure.

1.1. Goals

Jakarta Data addresses a fundamental challenge in Java application development: the seamless integration of diverse data sources amid the dissimilarities in their respective programming models. Offering the Java developer a common, familiar starting point for data access is helpful for solutions involving multiple databases and storage technologies.

The primary problem Jakarta Data sets out to solve is the complexity and inconsistency that arises when Java applications encounter various database systems—relational, document, column, key-value, graph, and others. Managing these diverse data sources can be daunting, often requiring developers to write specialized code for each storage technology.

Jakarta Data combines the concept of a persistence agnostic API with a domain-centric approach. This approach enables developers to work with different databases and storage engines while aligning their data access strategies with the core principles of a domain-centric architecture, where the domain model plays a central role in shaping the application’s structure.

Jakarta Data is guided by a set of clear and well-defined objectives to simplify data integration and enhance data access for Java developers. These objectives serve as the pillars of its design philosophy, ensuring that it addresses real-world challenges and provides concrete advantages to developers:

  • Jakarta Data is engineered to tackle a fundamental problem: simplifying data access and manipulation within Java applications that interact with diverse databases and storage sources.

  • Jakarta Data is designed to be persistence agnostic: In this context, agnostic does not mean that you can switch the underlying persistence without changes but implies that Jakarta Data is not tied to a specific database technology. It offers a flexible, adaptable framework that allows you to work with the databases and storage sources that best suit your project’s needs. This agnostic approach ensures that Jakarta Data can cater to various use cases.

  • Enhancing a Domain-Centric Approach: Jakarta Data enhances the concept of a persistence agnostic API by incorporating a domain-centric approach. It enables developers to align their data access strategies with the core principles of a domain-centric architecture, where the domain model plays a central role in shaping the application’s structure.

  • Unified API: Jakarta Data provides a unified and standardized API for interacting with various data sources. This consistency simplifies development by allowing developers to use the same tools and practices regardless of the underlying database technology.

  • Pluggable and Extensible: Jakarta Data is designed to be pluggable and extensible. Even in cases where the API doesn’t directly support a specific behavior of a storage engine, Jakarta Data aims to provide an extensible API to enable developers to customize and adapt as needed.

  • Simplified and Domain-Centric Querying and Database Operations: Jakarta Data strongly emphasizes simplifying and aligning querying and database operations with your application’s domain model. By offering domain-centric query capabilities through annotations, built-in repository interfaces, or query-by-method, Jakarta Data strives to be compatible with multiple databases and inherently closer to your application’s domain logic. This approach ensures that your queries and operations are more versatile across various persistence engines, making working with different data sources easier while maintaining a cohesive and domain-focused codebase.

  • Seamless Integration: Jakarta Data enables seamless integration between Java applications and various persistence layers, making it easier for developers to work with different databases and storage sources without extensive customization.

1.2. Non-Goals

The following are not goals of Jakarta Data:

  1. Specific Features of Jakarta Persistence, Jakarta NoSQL, etc., and Specializations: Jakarta Data does not intend to replicate or replace the specific features provided by other Jakarta specifications, such as Jakarta Persistence and Jakarta NoSQL, along with their associated specializations and extensions. These specifications have well-defined scopes and functionalities that cater to specific use cases. Jakarta Data operates with the understanding that it complements these specifications by providing a higher-level, agnostic API. It does not seek to duplicate their capabilities but aims to simplify data access and integration across diverse data sources.

  2. Replacement of Jakarta Persistence or Jakarta NoSQL Specifications: Jakarta Data’s primary goal is not to replace or supersede the Jakarta Persistence or Jakarta NoSQL specifications. Instead, it works in harmony with these specifications, serving as an additional layer that abstracts the complexities of data access. Jakarta Data enhances the developer experience by offering a persistence-agnostic approach while leveraging the capabilities of Jakarta Persistence and Jakarta NoSQL. Its role is to complement and simplify, not replace, these established specifications.

1.3. Conventions

The terms, entity attribute and entity property, are used interchangeably throughout the specification.

The lower case terms, slice and page, are used interchangeably throughout the specification. Where there is a need to distinguish, the class name, Slice or Page, is used.

1.4. Jakarta Data Project Team

This specification is being developed as part of Jakarta Data project under the Jakarta EE Specification Process. It is the result of the collaborative work of the project committers and various contributors.

1.4.1. Project Leads

1.4.3. Mentor

1.4.4. Contributors

The complete list of Jakarta Data contributors may be found here.

2. Repository

In the realm of software design, the repository pattern encapsulates the logic required to access data sources. This pattern consolidates data access functionality, offering improved maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

repository structure

The Repository pattern is a fundamental concept within Jakarta Data that plays a central role in data access and management. Essentially, a repository is a mediator between an application’s domain logic and the underlying data storage, be it a relational database, NoSQL database, or any other data source.

In Jakarta Data, a Repository provides a structured and organized way to interact with data. It abstracts data storage and retrieval complexities, allowing you to work with domain-specific objects and perform common operations on data without writing low-level database queries.

As employed in Jakarta Data, the Repository pattern exhibits several key characteristics that make it a powerful tool for managing data access within Java applications. These characteristics collectively define how repositories function within Jakarta Data, providing a structured and domain-centric approach to working with data. These key characteristics offer insight into how repositories simplify data access and enhance the maintainability of code.

  • Abstraction: Repositories abstract the details of how data is stored, enabling the developer to focus on the application’s domain logic without being tightly coupled to a specific database technology.

  • Structured Data Access: Jakarta Data repositories offer a structured and consistent way to perform data access operations. This structured approach ensures that the codebase remains organized and maintainable.

  • Domain-Centric: Repositories are designed to be domain-centric, aligning with the application’s domain model. It means that data access operations are closely tied to business entities, making code more intuitive and expressive.

In summary, the Repository pattern in Jakarta Data offers a structured and domain-centric approach to data access, providing a balance between abstraction and ease of use. It simplifies data access by encapsulating the details of the data source while aligning closely with the application’s domain model. It makes it a valuable choice for many Java developers, especially in projects where a clean separation of concerns and maintainable codebase are essential.

2.1. Repositories in Jakarta Data

Within the context of Jakarta Data, the repository plays a pivotal role in simplifying data access for various persistence stores. The repository is a Java interface that acts as a gateway for accessing persistent data of one or more entity types. Repositories offer a streamlined approach to working with data by exposing operations for querying, retrieving, and modifying entity class instances that represent data in the persistent store.

Several characteristics define repositories:

  • Reduced Boilerplate Code: One of the primary goals of a repository abstraction is to significantly reduce the boilerplate code required to implement data access layers for diverse persistence stores. This reduction in repetitive code enhances code maintainability and developer productivity.

  • Jakarta Data Annotations: In Jakarta Data, repositories are defined as interfaces and are annotated with the @Repository annotation. This annotation serves as a marker to indicate that the interface represents a repository.

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

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

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

  • Entity Associations: Entities within a repository may have associations between them, especially in the case of relational data access. However, this specification does not define the semantics of associations between entities belonging to different repositories.

  • Stateless Repositories: Repositories are stateless. This specification does not address the definition of repositories that externalize Jakarta Persistence-style stateful persistence contexts.

Repositories in Jakarta Data serve as efficient gateways for managing and interacting with persistent data, offering a simplified and consistent approach to data access and modification within Java applications.

The application must provide the following when using repositories in Jakarta Data:

  1. Entity Classes and Mappings: Developers define a set of entity classes and mappings tailored to a specific data store. These entities represent the data structure and schema, offering a powerful means to interact with the underlying data.

  2. Repository Interfaces: Jakarta Data enables the creation of one or more repository interfaces, following predefined rules that include the guidelines set forth by this specification. These interfaces are the gateways to accessing and manipulating the data, offering a structured and efficient way to perform data operations.

An implementation of Jakarta Data, specifically tailored to the chosen data store, assumes the responsibility of implementing each repository interface. This symbiotic relationship between developers and Jakarta Data ensures that data access and manipulation remain consistent, efficient, and aligned with best practices.

Jakarta Data empowers developers to shape their data access strategies by defining entity classes and repositories, with implementations seamlessly adapting to the chosen data store. This flexibility and Jakarta Data’s persistence-agnostic approach promote robust data management within Java applications.

The Jakarta Data specification supports two types of repositories.

2.1.1. Repositories with Built-in Supertypes

The first type consists of built-in interfaces that are parent interfaces from which repositories can inherit. At the root of this hierarchy is the DataRepository interface. These built-in interfaces are extensible, meaning a repository can extend one or more of them or none at all. When a repository extends a built-in interface, the method signatures copied from the built-in interfaces must retain the same behavior as defined in the built-in interfaces.

Diagram

The BasicRepository interface includes some of the most common operations, which applies to single type of entity, designated via its first parameterized type variable.

The CrudRepository interface inherits from BasicRepository, adding Insert and Update operations that correspond to Create and Update in the CRUD (Create, Read, Update, Delete) pattern.

The PageableRepository interface inherits from BasicRepository, adding built-in methods that leverage the pagination feature.

The Java developer creates an interface that is annotated with the @Repository annotation and optionally extends one of the built-in repository interfaces.

Given a Product entity where the ID is a long type, the repository can be:

@Repository
public interface ProductRepository extends BasicRepository<Product, Long> {

}

There is no nomenclature restriction to require the Repository suffix. For example a repository for Car entities can be named Cars, Vehicles, or even Garage instead of CarRepository.

@Repository
public interface Garage extends BasicRepository<Car, String> {

}

2.1.2. Repositories without Built-in Supertypes

Additionally, Jakarta Data allows for custom interfaces that do not extend any built-in interfaces. These non-built-in interfaces enable developers to define the repository structures and behavior and provide a means to define your domain’s ubiquitous language precisely.

In this context, database operations involving fundamental data changes, such as insertion, update, and removal, are realized through the strategic utilization of annotations like Insert, Update, Delete, and Save. These annotations enable the crafting of expressive and contextually meaningful repository methods, resulting in a repository that closely mirrors the semantics of your domain.

For instance, consider the Garage repository interface below:

@Repository
public interface Garage {

    @Insert
    Car park(Car car);

    @Delete
    void unpark(Car car);
}

Here, the @Insert annotation is used for the park method, allowing you to design a repository interface that encapsulates the essence of your domain. This approach fosters a shared understanding and more intuitive communication within your development team, ensuring that your database operations are integral to your domain’s language.

Jakarta Data allows applications to intermix both patterns by defining methods that are annotated with Insert, Update, Delete, or Save on repositories that inherit from the built-in supertypes.

3. Entity Classes

In Jakarta Data, an entity refers to a fundamental data representation and management building block. It can be conceptually understood in several aspects:

  1. Entity Classes: Entity classes are simple Java objects equipped with fields or accessor methods that designate each property of the entity. Depending on your data storage needs, you may use annotations from the Jakarta Persistence specification, such as jakarta.persistence.Entity, jakarta.persistence.Id, and jakarta.persistence.Column, to define and customize entities for relational databases. Alternatively, for NoSQL databases, you can use annotations from the Jakarta NoSQL specification, including jakarta.nosql.Entity, jakarta.nosql.Id, and jakarta.nosql.Column.

  2. Data Schema: Abstractly, an entity or entity type serves as a schema for data. It defines the structure and properties of the data it represents. This schema can be as simple as a set of typed fields, similar to the relational model, or more structured, as found in document data stores. The schema can be explicitly defined, as in the case of SQL Data Definition Language (DDL) declarations for relational tables, or it can be implicit, common in key/value stores.

  3. Persistence and Representation: Entities are associated with persistent data, meaning the data outlives any specific Java process utilizing it. Each persistent instantiation of the schema is distinguishable by a unique identifier. For example, a row of a relational database table is identifiable by the value of its primary key. In Java, these entities are represented as classes, referred to as entity classes. It’s important to note that multiple instances of the entity class within a Java program can represent a single persistent instance of the schema.

  4. Provider Differentiation: To maintain clarity and specify the desired provider when using Jakarta Data, it is recommended that applications do not mix Entity annotations from different models. This practice allows the Entity annotation to indicate the desired provider, especially in cases where multiple types of Jakarta Data providers are available. A Jakarta Data provider must provide implementation of repositories for Entity types having the Entity annotations that it supports, ignoring Entity types only having identifiable Entity annotations that the Jakarta Data provider does not support. The latter are to be handled by other Jakarta Data providers that do support the other types of Entity annotations.

An entity within Jakarta Data encompasses the Java class representing the data and the schema, persistence characteristics, and provider-specific annotations, all working together to simplify data access and management within Java applications. While a Jakarta Data provider might require a default constructor and work primarily with mutable entities, Jakarta Data allows for the use of immutable entity classes, which are best represented as Java records.

3.1. Programming Model for Entity Data in Jakarta Data

Jakarta Data does not define an entity model of its own, instead borrowing the entity models of other Jakarta standards (Jakarta Persistence and Jakarta NoSQL) and allowing for vendor-specific entity models to be used. This section defines core concepts that entity models must follow in order to be used with Jakarta Data. The entity model allows the user to define Java classes that represent data entities. These entities can be stored, retrieved, and manipulated in various databases, including key-value, wide-column, document, graph, and relational databases. A programming model for entities ensures that entity classes are well-defined and can be seamlessly integrated with different database technologies.

Jakarta Data places requirements on two types of fields within entity classes: basic fields and relation fields. Basic fields represent fundamental data types natively supported by Jakarta Data Providers. Support for basic types is mandatory for all Jakarta Data providers. On the other hand, Domain-Relation fields allow entities to interact with other domain classes or types, enriching data structures, but also making them more complex. Support for Domain-Relation fields varies depending on the Jakarta Data provider and the database type. Jakarta Data does not require support for Domain-Relation entity fields when using Graph databases.

3.1.1. Basic Types

A variety of basic types can be used for fields or properties of entity classes. The basic types include:

Basic Data Type Description

Primitive Types and Wrappers

All Java primitive types, such as int, double, boolean, etc., and their corresponding java.lang wrapper types (e.g., Integer, Double, Boolean).

String

Represents text data.

LocalDate, LocalDateTime, LocalTime, Instant

Represent date and time-related data.

UUID

Universally Unique Identifier for identifying entities.

BigInteger, BigDecimal

Represent large integer and decimal numbers.

byte[]

Represents binary data.

Enum Types

Custom enumerated types defined by developers.

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).

It is 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.

In addition to the basic types, entity models might also choose to support additional types that represent data in a domain-specific manner. These custom types may include complex data structures and objects. Entity models can choose to provide mechanisms to convert these custom types to the supported basic types.

3.1.2. Domain-Relation Fields in Jakarta Data

In Jakarta Data, the concept of Domain-Relation fields encompasses two distinct types: component fields and association fields. These fields enable developers to establish relationships between entities and other domain concepts, enriching the complexity and structure of data entities.

  • Component Fields: A component field represents a relationship where one entity is treated as a component of another entity. It implies that the component does not have a life cycle outside the entity to which it belongs. It is an embedded object that exists solely within the context of the owning entity.

  • Association Fields: Association fields represent semantically weak relationships, often called semantic dependencies, between objects that may not have a direct or strong connection. Unlike component fields, association fields may exist between entities that are otherwise unrelated. Associations can further specialize into aggregation, a specific form of association in which each object involved has its life cycle, yet a notion of ownership also exists.

Definition and implementation of Domain-Relation fields may vary across different Jakarta Data providers. Providers can create annotations, define conventions, or leverage standards like Jakarta Persistence to establish these relationships effectively.

The topic of serialization of Domain-Relation fields within databases is crucial for maintaining data consistency and integrity. The next section discussed how Jakarta Data providers handle the persistence and serialization of Domain-Relation fields for various database types.

In Jakarta Data, the serialization of Domain-Relation fields, such as components, can be achieved in two ways.

3.1.2.1. Merging Fields Directly in the Entity (Component Embedding)

In this approach, a component merges its fields directly within the entity. From the perspective of the persistence layer, the component itself doesn’t exist as a separate table or document. Instead, it becomes part of the entity’s structure. This approach leads to a flat representation in the database.

Example:

public class Address {

    private String street;
    private String city;
    private String postalCode;
}

//the entity
public class Person {
    private Long id;

    private String name;
    private Address address;  // This is a component field
}

The structure of the entity in a Document, Wide-Column, and Graph database will be like the JSON representation below. The JSON representation is for illustration; the actual representation is an implementation detail of the database. The JSON and the following SQL table are representations resulting from the Java classes.

{
  "id": 1,
  "name": "John Doe",
  "street": "123 Main St",
  "city": "Sampleville",
  "postalCode": "12345"
}
id name street city postalCode

1

John Doe

123 Main St

Sampleville

12345

This approach allows for a flat and denormalized structure in the database, making it suitable for a variety of database types.

3.1.2.2. Storing Components in Separate Tables (Relational Databases) or as Subdocuments/UDTs (NoSQL Databases)

This approach is typically employed for more complex relationships and associations within the domain model, allowing for greater flexibility and scalability. It involves storing components, such as the Passport in the example, in separate tables for relational databases or as subdocuments or User-Defined Types (UDTs) for NoSQL databases. This method is suitable for scenarios where an association exists between one entity and another.

For instance, consider the Citizen and Passport classes. In a relational database, this approach results in two separate tables, each representing an entity with its associated persistence context. In contrast, for a NoSQL database like a Document database, the Passport can still act as a component within the Citizen entity, signifying that a Passport is closely tied to a Citizen. However, the modeling may vary depending on the specific NoSQL database and its capabilities.

public class Passport {
    private Long id;
    private String passportNumber;
    private LocalDate expirationDate;
}

public class Citizen {
    private Long id;
    private String name;
    private Passport passport; // One-to-One relationship with Passport for relational database
}

Here are some possible representations of the Citizen and Passport entities in table and JSON formats:

Table 1. Citizen Table:
id (Primary Key) name passport_id (Foreign Key)

1

John Doe

1

Table 2. Passport Table:
id (Primary Key) passportNumber expirationDate

1

A123456

2023-12-31

These tables represent the data from the JSON structure you provided earlier. In a relational database, the Citizen and Passport entities are stored in separate tables, connected by a foreign key relationship.

{
  "id": 1,
  "name": "John Doe",
  "passport": {
    "passportNumber": "A123456",
    "expirationDate": "2023-12-31"
  }
}

Entities in a domain model often have relationships with other entities. In some cases, an entity may have a collection of another type of entity, creating a one-to-many or many-to-many relationship. This scenario explores how such relationships are represented and managed in the context of Jakarta Data.

Consider the example of an Author entity associated with multiple Book entities. This relationship allows an author to be linked to multiple books they have authored. While this structure remains relatively unchanged for NoSQL databases, it introduces specific considerations in relational databases, where it typically generates auxiliary tables to manage the relationship. We’ll explore these representations in both JSON and relational database formats.

// Entity
public class Author {

    private UUID id;

    private String name;

    private List<Book> books;
}

// If in a relational database, Book might also be an entity
public class Book {

    // This field might not be required for some NoSQL database modeling
    private Long id;

    private String title;

    private String category;

    // Other fields and methods
}
{
  "id": "6f6d665d-5585-46cd-8b9b-61a559de0e13",
  "name": "John Doe",
  "books": [
    {
      "title": "Sample Book 1",
      "category": "Fiction"
    },
    {
      "title": "Sample Book 2",
      "category": "Non-fiction"
    }
  ]
}
{
  "id": "218828d0-3215-08fe-937f-42b5119c8f22",
  "name": "Jane Smith",
  "books": [
    {
      "title": "Sample Book 2",
      "category": "Non-fiction"
    }
  ]
}

In the JSON representation, an Author entity can be associated with multiple Book entities within an array.

Here are the tables with content based on the JSON data structure:

Table 3. Author Table:
id (Primary Key) name

1

John Doe

2

Jane Smith

Table 4. Book Table:
id (Primary Key) title category

1

Sample Book 1

Fiction

2

Sample Book 2

Non-fiction

Table 5. Author_Book (Auxiliary) Table:
author_id (Foreign Key) book_id (Foreign Key)

1

1

1

2

2

2

These tables represent the data from the JSON structure provided earlier, illustrating a many-to-many relationship between authors and books using an auxiliary table.

In a relational database, this relationship typically generates three tables: Author, Book, and an auxiliary table Author_Book to manage the many-to-many relationship between authors and books.

In some scenarios, books can have multiple authors, and authors can contribute to several books, resulting in a many-to-many (N-N) cardinality relationship. Jakarta Data offers flexibility in representing and managing such complex relationships.

This N-N relationship typically generates a dedicated table to manage the associations between books and authors in a relational database. However, NoSQL databases may take a different approach, especially in cases where denormalization and data duplication are favored for query-driven designs.

Key-value databases might support these fields, generating a single BLOB value. However, the serialization process for such fields may vary depending on the Jakarta Data provider and the specific key-value database used.
Graph databases are not required to support Domain-Relation types, but it might be used for aggregate query returns or as a read-only field.

3.1.3. Recursion in Domain-Relation Fields in Jakarta Data

In the context of Jakarta Data, the term recursion pertains to the ability to manage hierarchical or nested relationships between entities. This capability is essential when dealing with complex domain models involving associations, aggregations, or compositions between various entity types. Jakarta Data ensures these relationships are correctly mapped and maintained within the database, enabling consistent data retrieval and manipulation.

For relational databases, Jakarta Data requires support of recursive relationships. If one entity type is associated with or contains another entity type, the Jakarta Data provider for relational databases must establish the required table structures and foreign key constraints to uphold these relationships. This approach guarantees data integrity and consistency when working with the database.

For other types of databases, Jakarta Data does not require explicit support for recursive relationships. In NoSQL databases, data is often stored in a denormalized or nested document format, making it more challenging to enforce strict hierarchical relationships. Instead, NoSQL databases may emphasize query-driven design rather than explicit mapping or management of recursive relationships.

In instances where a Jakarta Data provider for NoSQL databases encounters a recursive relationship that it cannot support due to the specific characteristics of the database, it must throw a jakarta.data.exceptions.MappingException or an appropriate subclass of MappingException. This exception notifies developers that the database does not support the relationship.

3.1.4. Entity Property Names

Within an entity, property names must be unique ignoring case. For simple entity properties, the field or accessor method name serves as the entity property name. In the case of embedded classes, entity property names are computed by concatenating the field or accessor method names at each level, optionally joined by a delimiter.

Locations where entity property names can be used, along with delimiters, are shown in the table. The examples in the table assume an Order entity has an address of type MailingAddress with a zipCode of type int.

Table 6. Locations of Entity Properties and Delimiters Table:
Entity Property Location Delimiter Example

@Query annotation

.

@Query("SELECT o FROM Order o WHERE o.address.zipCode=?1")

Query by Method Name method name

_

List<Order> findByAddress_zipCode(int zip);

Parameter-based Conditions parameter name

_

List<Order> find(int address_zipCode);

Sort property value

. or _

Sort.asc("address_zipCode")

@By or @OrderBy annotation value

. or _

List<Order> find(@By("address.zipCode") int zip);

For a given entity property name, delimiter usage must be consistent. Either the delimiter must always be used within the entity property name to delimit subcomponents or the delimiter must never be used within the entity property name. Except in the case of @Query where the delimiter is required by the query language, delimiters can be omitted entirely from an entity property name when it is unnecessary to disambiguate the entity property to which the name refers.

The resolution of properties involves the following steps:

  1. Property Extraction: The framework obtains entity property names from the locations in the above table, applying the BNF grammar in the case of Query by Method Name to extract entity property names from the method name. For example, for the query method findByAddressZipCode, the property name extracted is AddressZipCode.

  2. Property Lookup on Entity: The framework compares the extracted name, ignoring case, against the field names of the respective entity class.

  3. Property Lookup in Hierarchy: If no match was found among the entity field names, the framework compares the extracted name, ignoring case, against the combination of the field names of the respective entity and the fields of the entity’s hierarchy of relations and embedded classes, concatenated as outlined in this section above, both with and without the optional delimiter.

  4. Resolution Outcome: If the framework successfully identifies a property in the domain class or along the specified traversal path that matches the extracted property name, it uses that property.

When @Query is used, the Jakarta Data specification defers to the rules of the query language on whether the delimiter is required and whether the case must match.

Users are encouraged to follow Java’s camel case naming standards for fields of entities, relations, and embedded classes, avoiding underscores in field names. The resolution algorithm for property identification relies on traversal with underscores. Adhering to camel case for property names ensures consistency and eliminates ambiguity, enabling effective data filtering and retrieval from domain classes.

3.1.4.1. Scenario 1: Person Repository with Unambiguous Resolution

In this scenario, we have the following data model:

class Person {
  private Long id;
  private MailingAddress address;
}

class MailingAddress {
  private int zipcode;
}

The Person entity does not have an addressZipCode field, so use of the delimiter is optional. It is valid to write both of the following repository methods, which have the same meaning,

List<Person> findByAddressZipCode(int zipCode);
List<Person> findByAddress_zipcode(int zipCode);
3.1.4.2. Scenario 2: Order Repository with Resolution that requires a Delimiter

In this scenario, we have the following data model:

class Order {
  private Long id;
  private String addressZipCode;
  private MailingAddress address;
}

class MailingAddress {
  private int zipcode;
}

The Order entity has an addressZipCode field, as well as an address field for an embeddable class with a zipcode field. The method name findByAddressZipCode points to the addressZipCode field and cannot be used to navigate to the embedded class. To navigate to the zipcode field of the embedded class, the delimiter must be used:

List<Order> findByAddress_zipcode(int zipCode);
Define entity properties following standard Java naming conventions for camel case, using underscore only as the last resort.

In all places where entity property names can be specified other than @Query, Id is an alias for the entity property that is designated as the id. Entity property names that are used in Query by Method Name must not contain reserved words.

4. Repository Interfaces

A Jakarta Data repository is a Java interface annotated with @Repository. A repository interface may declare:

  • abstract (non-default) methods, and

  • concrete (default) methods.

A concrete method may call other methods of the repository, including abstract methods.

Every abstract method of the interface is usually either:

  • an entity instance lifecycle method,

  • an annotated query method,

  • an automatic query method (with Parameter-based conditions or Query by Method Name), or

  • a resource accessor method.

A repository may declare lifecycle methods for a single entity type, or for multiple related entity types. Similarly, a repository might have query methods which return different entity types.

A repository interface may inherit methods from a superinterface. A Jakarta Data implementation must treat inherited abstract methods as if they were directly declared by the repository interface. For example, a repository interface may inherit the CrudRepository interface defined by this specification.

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 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, life cycle methods on the repository determine the primary entity type. To do so, all life cycle 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.

A Jakarta Data provider might go beyond what is required by this specification and support abstract methods which do not fall into any of the above categories. Such functionality is not defined by this specification, and so applications with repositories which declare such methods are not portable between providers.

The subsections below specify the rules that an abstract method declaration must observe so that the Jakarta Data implementation is able to provide an implementation of the abstract method.

  • If every abstract method of a repository complies with the rules specified below, then the Jakarta Data implementation must provide an implementation of the repository.

  • Otherwise, if a repository declares an abstract method which does not comply with the rules specified below, or makes use of functionality which is not supported by the Jakarta Data implementation, then an error might be produced by the Jakarta Data implementation at build time or at runtime.

The portability of a given repository interface between Jakarta Data implementations depends on the portability of the entity types it uses. If an entity class is not portable between given implementations, then any repository which uses the entity class is also unportable between those implementations.

Additional portability guarantees may be provided by specifications which extend this specification, specializing to a given class of datastore.

4.1. Lifecycle methods

A lifecycle method is an abstract method annotated with a lifecycle annotation. Lifecycle methods allow the program to make changes to persistent data in the data store.

A lifecycle method must be annotated with a lifecycle annotation. The method signature of the lifecycle method, including its return type, must follow the requirements that are specified by the JavaDoc of the lifecycle annotation.

Lifecycle methods follow the general pattern:

@Lifecycle
ReturnType lifecycle(Entity e);

where lifecycle is the arbitrary name of the method, Entity is a concrete entity class or an Iterable or array of this entity, Lifecycle is a lifecycle annotation, and ReturnType is a return type that is permitted by the lifecycle annotation JavaDoc.

This specification defines four built-in lifecycle annotations: @Insert, @Update, @Delete, and @Save.

For example:

@Insert
void insertBook(Book book);

Lifecycle methods are not guaranteed to be portable between all providers.

Jakarta Data providers must support lifecycle methods to the extent that the data store is capable of the corresponding operation. If the data store is not capable of the operation, the Jakarta Data provider must raise UnsupportedOperationException when the operation is attempted, per the requirements of the JavaDoc for the lifecycle annotation, or the Jakarta Data provider must report the error at compile time.

There is no special programming model for lifecycle annotations. The Jakarta Data implementation automatically recognizes the lifecycle annotations it supports.

A Jakarta Data provider might extend this specification to define additional lifecycle annotations, or to support lifecycle methods with signatures other than the usual signatures defined above. For example, a provider might support "merge" methods declared as follows:

@Merge
Book mergeBook(Book book);

Such lifecycle methods are not portable between Jakarta Data providers.

4.2. Annotated Query methods

An annotated query method is an abstract method annotated by a query annotation type. The query annotation specifies a query in some datastore-native query language.

Each parameter of an annotated query method must either:

  • have exactly the same name 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, Pageable, or Sort.

A repository with annotated query methods with named parameters must be compiled so that parameter names are preserved in the class file (for example, using javac -parameters), or the parameter names must be specified explicitly using the @Param annotation.

An annotated query method must not also be annotated with a lifecycle annotation.

The return type of the annotated query method must be consistent with the result type of the query specified by the query annotation.

The result type of a query depends on datastore-native semantics, and so the return type of an annotated query method cannot be specified here. However, Jakarta Data implementations are strongly encouraged to support the following return types:

  • for a query which returns a single result of type T, the type T itself, or Optional<T>,

  • for a query which returns many results of type T, the types List<T>, Page<T>, and T[].

Furthermore, implementations are encouraged to support void as the return type for a query which never returns a result.

This specification defines the built-in @Query annotation, which may be used to specify a query in an arbitrary query language understood by the Jakarta Data provider.

For example, using a named parameter:

@Query("where title like :title order by title")
Page<Book> booksByTitle(String title, Pageable page);
@Query("SELECT p FROM Product p WHERE p.name=:prodname")
Optional<Product> findByName(@Param("prodname") String name);

Or, using a positional parameter:

@Query("delete from Book where isbn = ?1")
void deleteBook(String isbn);

Programs which make use of annotated query methods are not portable between providers.

A Jakarta Data provider might extend this specification to define its own query annotation types. For example, a provider might define a @SQL annotation for declaring queries written in SQL.

There is no special programming model for query annotations. The Jakarta Data implementation automatically recognizes the query annotations it supports.

4.3. Parameter-based automatic query methods

An automatic query method is an abstract method that either generates a query based on the parameters of the method or based on the name of the method (the Query by Method Name pattern is discussed separately). The method return type identifies the entity. For example: E, Optional<E>, Page<E>, or List<E>, where E is an entity class. Each parameter must either:

  • have exactly the same type and name as a persistent field or property of the entity class, or

  • be of type Limit, Pageable, or Sort.

A repository with automatic query methods that are based on parameters must either be compiled so that parameter names are preserved in the class file (for example, using javac -parameters), or the corresponding entity attribute name for parameters must be specified explicitly using the @By annotation.

For example:

Book bookByIsbn(String isbn);

List<Book> booksByYear(Year year, Sort order, Limit limit);

Page<Book> find(@By("year") Year publishedIn,
                @By("genre") Category type,
                Pageable pagination);

Automatic query methods are portable between providers.

4.4. Resource accessor methods

A resource accessor method is a method with no parameters which returns a type supported by the Jakarta Data provider. The purpose of this method is to provide the program with direct access to the data store.

For example, if the Jakarta Data provider is based on JDBC, the return type might be java.sql.Connection or javax.sql.DataSource. Or, if the Jakarta Data provider is backed by Jakarta Persistence, the return type might be jakarta.persistence.EntityManager.

The Jakarta Data provider recognizes the connection types it supports and implements the method such that it returns an instance of the type of resource. If the resource type implements java.lang.AutoCloseable and the resource is obtained within the scope of a default method of the repository, then the Jakarta Data provider automatically closes the resource upon completion of the default method. If the method for obtaining the resource is invoked outside the scope of a default method of the repository, then the user is responsible for closing the resource instance.

A Jakarta Data implementation might allow a resource accessor method to be annotated with additional metadata providing information about the connection.

For example:

Connection connection();

default void cleanup() {
    try (Statement s = connection().createStatement()) {
        s.executeUpdate("truncate table books");
    }
}

A repository may have at most one resource accessor method.

4.5. Query by Method Name

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

@Repository
public interface ProductRepository extends BasicRepository<Product, Long> {

  List<Product> findByName(String name);

  @OrderBy("price")
  List<Product> findByNameLike(String namePattern);

  @OrderBy(value = "price", descending = true)
  List<Product> findByNameLikeAndPriceLessThan(String namePattern, float priceBelow);

}

The parsing of query method names follows a specific format:

  • The method name consists of the subject, the predicate, and optionally the order clause.

  • The subject begins with the action (such as find or delete) and is optionally followed by an expression (for example, First10), followed by any number of other characters, followed by By.

  • The predicate defines the query’s condition or filtering criteria, where multiple conditions are delimited by And or Or. For example, PriceLessThanAndNameLike.

  • The order clause, which is optional, begins with OrderBy and consists of an ordered collection of entity attributes by which to sort results, delimited by Asc or Desc to specify the sort direction of the preceding attribute.

  • The method name is formed by combining the subject, predicate, and order clause, in that order.

This specification uses the terms subject and predicate in a way that aligns with industry terminology rather than how they are defined in English grammar.

Queries can also handle entities with relation attributes by specifying the relationship using dot notation, with the dot converted to underscore so that it is a valid character within the method name. See Scenario 3 below for an example.

Example query methods:

  • findByName(String name): Find entities by the 'name' property.

  • findByAgeGreaterThan(int age): Find entities where 'age' is greater than the specified value.

  • findByAuthorName(String authorName): Find entities by the 'authorName' property of a related entity.

  • findByCategoryNameAndPriceLessThan(String categoryName, double price): Find entities by 'categoryName' and 'price' properties, applying an 'And' condition.

4.5.1. BNF Grammar for Query Methods

Query methods allow developers to create database queries using method naming conventions. These methods consist of a subject, predicate, and optional order clause. This BNF notation provides a structured representation for understanding and implementing these powerful querying techniques in your applications.

<query-method> ::= <subject> <predicate> [<order-clause>]
<subject> ::= (<action> | "find" <find-expression>) [<ignored-text>] "By"
<action> ::= "find" | "delete" | "update" | "count" | "exists"
<find-expression> ::= "First" [<positive-integer>]
<predicate> ::= <condition> { ("And" | "Or") <condition> }
<condition> ::= <property> ["IgnoreCase"] ["Not"] [<operator>]
<operator> ::= "Contains" | "EndsWith" | "StartsWith" | "LessThan"| "LessThanEqual" | "GreaterThan" | "GreaterThanEqual" | "Between" | "Empty" | "Like" | "In" | "Null" | "True" | "False"
<property> ::= <identifier> | <identifier> "_" <property>
<identifier> ::= <word>
<positive-integer> ::= <digit> { <digit> }
<order-clause> ::= "OrderBy" { <order-item> } ( <order-item> | <property> )
<order-item> ::= <property> ("Asc" | "Desc")

Explanation of the BNF elements:

  • <query-method>: Represents a query method, which consists of a subject, a predicate, and an optional order clause.

  • <subject>: Defines the action (e.g., "find" or "delete") followed by an optional expression and "By."

  • <action>: Specifies the action, such as "find" or "delete."

  • <find-expression>: Represents an optional expression for find operations, such as "First10."

  • <ignored-text>: Optional text that does not contain "By".

  • <predicate>: Represents the query’s condition or filtering criteria, which can include multiple conditions separated by "And" or "Or."

  • <condition>: Specifies a property and an operator for the condition.

  • <operator>: Defines the operator for the condition, like "Between" or "LessThan."

  • <property>: Represents a property name, which can include underscores for nested properties.

  • <identifier>: Represents a word (e.g., property names, action names, etc.).

  • <positive-integer>: Represents a whole number greater than zero.

  • <order-clause>: Specifies the optional order clause, starting with "OrderBy" and followed by one or more order items.

  • <order-item>: Represents an ordered collection of entity attributes by which to sort results, including an optional "Asc" or "Desc" to specify the sort direction.

4.5.2. Query by Method Name Keywords

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

Keyword Description Not Required For

findBy

General query method returning entities.

Key-value, Wide-Column

deleteBy

Delete query method returning either no result (void) or the delete count.

Key-value, Wide-Column

countBy

Count projection returning a numeric result.

Key-value, Wide-Column

existsBy

Exists projection, returning as a boolean result.

Key-value, Wide-Column

The "Not Required For" column indicates the database types for which the respective keyword is not required or applicable.

Jakarta Data implementations must support the following list of Query by Method Name keywords, except where indicated for a database type. A repository method must raise java.lang.UnsupportedOperationException or a more specific subclass of the exception if the database does not provide the requested functionality.

Keyword Description Method signature Sample Not Required For

And

The And operator requires both conditions to match.

findByNameAndYear

Key-value, Wide-Column

Or

The Or operator requires at least one of the conditions to match.

findByNameOrYear

Key-value, Wide-Column

Not

Negates the condition that immediately follows the Not keyword. When used without a subsequent keyword, means not equal to.

findByNameNotLike

Key-value, Wide-Column

Between

Find results where the property is between (inclusive of) the given values

findByDateBetween

Key-value, Wide-Column

Contains

For Collection attributes, matches if the collection includes the value. For String attributes, a substring of the String must match the value, which can be a pattern.

findByPhoneNumbersContains

Key-value, Wide-Column, Document

Empty

Find results where the property is an empty collection or has a null value.

deleteByPendingTasksEmpty

Key-value, Wide-Column, Document, Graph

EndsWith

Matches String values with the given ending, which can be a pattern.

findByProductNameEndsWith

Key-value, Wide-Column, Document, Graph

First

For a query with ordered results, limits the quantity of results to the number following First, or if there is no subsequent number, to a single result.

findFirst10By

Key-value, Wide-Column, Document, Graph

LessThan

Find results where the property is less than the given value

findByAgeLessThan

Key-value, Wide-Column

GreaterThan

Find results where the property is greater than the given value

findByAgeGreaterThan

Key-value, Wide-Column

LessThanEqual

Find results where the property is less than or equal to the given value

findByAgeLessThanEqual

Key-value, Wide-Column

GreaterThanEqual

Find results where the property is greater than or equal to the given value

findByAgeGreaterThanEqual

Key-value, Wide-Column

Like

Matches String values against the given pattern.

findByTitleLike

Key-value, Wide-Column, Document, Graph

IgnoreCase

Requests that string values be compared independent of case for query conditions and ordering.

findByStreetNameIgnoreCaseLike

Key-value, Wide-Column, Document, Graph

In

Find results where the property is one of the values that are contained within the given list

findByIdIn

Key-value, Wide-Column, Document, Graph

Null

Finds results where the property has a null value.

findByYearRetiredNull

Key-value, Wide-Column, Document, Graph

StartsWith

Matches String values with the given beginning, which can be a pattern.

findByFirstNameStartsWith

Key-value, Wide-Column, Document, Graph

True

Finds results where the property has a boolean value of true.

findBySalariedTrue

Key-value, Wide-Column

False

Finds results where the property has a boolean value of false.

findByCompletedFalse

Key-value, Wide-Column

OrderBy

Specify a static sorting order followed by the property path and direction of ascending.

findByNameOrderByAge

Key-value, Wide-Column

OrderBy____Desc

Specify a static sorting order followed by the property path and direction of descending.

findByNameOrderByAgeDesc

Key-value, Wide-Column

OrderBy____Asc

Specify a static sorting order followed by the property path and direction of ascending.

findByNameOrderByAgeAsc

Key-value, Wide-Column

OrderBy____(Asc|Desc)*(Asc|Desc)

Specify several static sorting orders

findByNameOrderByAgeAscNameDescYearAsc

Key-value, Wide-Column

The "Not Required For" column indicates the database types for which the respective keyword is not required or applicable.

4.5.2.1. Patterns

Wildcard characters for patterns are determined by the data access provider. For relational databases, _ matches any one character and % matches 0 or more characters.

4.5.2.2. Logical Operator Precedence

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.

4.5.3. 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 using Query by Method Name.

4.6. Special Parameter Handling

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

Jakarta Data recognizes, when specified on a repository method after the query parameters, the specific types, Limit, Pageable, and Sort, to dynamically apply limits, pagination, and sorting to queries, respectively. The following example demonstrates these features:

@Repository
public interface ProductRepository extends BasicRepository<Product, Long> {

  List<Product> findByName(String name, Pageable pageable);

  List<Product> findByNameLike(String pattern, Limit max, Sort... sorts);

}

You can define simple sorting expressions by using property names.

Sort name = Sort.asc("name");

You can combine sorting with a starting page and maximum page size by using property names.

Pageable pageable = Pageable.ofSize(20).page(1).sortBy(Sort.desc("price"));
first20 = products.findByNameLike(name, pageable);

4.7. 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.

4.7.1. Sort Criteria within Query Language

Sort criteria can be hard-coded directly within query language by making use of the @Query annotation. A repository method that is annotated with @Query with a value that contains an ORDER BY clause (or query language equivalent) must not provide sort criteria via the other mechanisms.

A repository method that is annotated with @Query with a value that does not contain an ORDER BY clause and ends with a WHERE clause (or query language equivalents to these) can use other mechanisms that are defined by this specification for providing sort criteria.

4.7.2. Static Mechanisms for Sort Criteria

Sort criteria is provided statically for a repository method by using the OrderBy keyword or by annotating the method with one or more @OrderBy annotations. The OrderBy keyword cannot be intermixed with the @OrderBy annotation or the @Query annotation. Static sort criteria takes precedence over dynamic sort criteria in that static sort criteria is evaluated first. When static sort criteria sorts entities to the same position, dynamic sort criteria is applied to further order those entities.

4.7.3. Dynamic Mechanisms for Sort Criteria

Sort criteria is provided dynamically to repository methods either via Sort parameters or via a Pageable parameter that has one or more Sort values. Sort and Pageable containing Sort must not both be provided to the same method.

4.7.4. Examples of Sort Criteria Precedence

The following examples work through scenarios where static and dynamic sort criteria are provided to the same method.

// Sorts first by type. When type is the same, applies the Pageable's sort criteria
Page<User> findByNameStartsWithOrderByType(String namePrefix, Pageable pagination);

// Sorts first by type. When type is the same, applies the criteria in the Sorts
List<User> findByNameStartsWithOrderByType(String namePrefix, Sort... sorts);

// Sorts first by age. When age is the same, applies the Pageable's sort criteria
@OrderBy("age")
Page<User> findByNameStartsWith(String namePrefix, Pageable pagination);

// Sorts first by age. When age is the same, applies the criteria in the Sorts
@OrderBy("age")
List<User> findByNameStartsWith(String namePrefix, Sort... sorts);

// Sorts first by name. When name is the same, applies the Pageable's sort criteria
@Query("SELECT u FROM User u WHERE (u.age > ?1)")
@OrderBy("name")
KeysetAwarePage<User> olderThan(int age, Pageable pagination);

4.8. Pagination in Jakarta Data

Dividing up large sets of data into pages is a beneficial strategy for data access and retrieval in many applications, including those developed in Java. Pagination helps improve the efficiency of handling large datasets in a way that is also user-friendly. In Jakarta Data, APIs are provided to help Java developers efficiently manage and navigate through data.

Jakarta Data supports two types of pagination: Offset and Keyset. These approaches differ in how they manage and retrieve paginated data:

Offset pagination is the more traditional form based on position relative to the first record in the dataset. It is typically used with a fixed page size, where a specified number of records is retrieved starting from a given offset position.

Keyset pagination, also known as seek method or cursor-based pagination, uses a unique key or unique combination of values (referred to as the keyset) to navigate the dataset relative to the first or last record of the current page. Keyset pagination is typically used with fixed page sizes but can accommodate varying the page size if desired. It is more robust when dealing with datasets where the underlying data might change and offers the the potential for improved performance by avoiding the need to scan records prior to the cursor.

The critical differences between Offset and Keyset pagination lie in their retrieval methods:

  • Offset pagination uses a fixed page size and retrieves data based on page number and size.

  • Keyset pagination relies on a unique key or unique combination of values (the keyset) for an entity relative to which it determines the next page or previous page.

4.8.1. Offset Pagination in Jakarta Data

Offset pagination is a popular method for managing and retrieving large datasets efficiently. It is based on dividing the dataset into pages containing a specified number of elements. This method allows developers to retrieve a subset of the dataset by identifying the page number and the maximum number of elements per page.

Offset pagination is motivated by the need to provide efficient navigation through large datasets. Loading an entire dataset into memory at once can be resource-intensive and lead to performance issues. By breaking the dataset into smaller, manageable pages, offset pagination improves performance, reduces resource consumption, and enhances the overall user experience.

Offset pagination offers several key features that make it a valuable approach for managing and retrieving large datasets in a controlled and efficient manner:

  • Page Size: The maximum number of elements to be included in each page is known as the page size. This parameter determines the subset of data retrieved with each pagination request.

  • Page Number: The page number indicates which subset of the dataset to retrieve. It typically starts from 1, representing the first page, and increments with each subsequent page.

  • Efficient Navigation: Offset pagination allows efficient dataset navigation. By specifying the desired page and page size, developers can control the data retrieved, optimizing memory usage and processing time.

  • Sequential Order: Elements are retrieved sequentially based on predefined criteria, such as ascending or descending order of a specific attribute, like an ID.

4.8.1.1. Requirements when using Offset Pagination

The following requirements must be met when using offset-based pagination:

  • The repository method signature must return Slice or Page. A repository method with return type of Slice or Page must raise UnsupportedOperationException if the database is incapable of offset pagination.

  • The repository method signature must accept a Pageable parameter.

  • Sort criteria must be provided and should be minimal.

  • The combination of provided sort criteria must define a deterministic ordering of entities.

  • The entities within each page must be ordered according to the provided sort criteria.

  • Except for the highest numbered page, the Jakarta Data provider must return full pages consisting of the maximum page size number of entities.

  • Page numbers for offset pagination are computed by taking the entity’s 1-based offset after sorting, dividing it by the maximum page size, and rounding up. For example, the 52nd entity is on page 6 when the maximum page size is 10, because 52 / 10 rounded up is 6. Note that the first page number is always 1.

4.8.1.2. Scenario: Person Entity and People Repository

Consider a scenario with a Person entity and a corresponding People repository:

public class Person {
    private Long id;
    private String name;
}

@Repository
public interface People extends PageableRepository<Person, Long> {
}

The dataset contains the following elements:

[
   {"id":1, "name":"Lin Le Marchant"},
   {"id":2, "name":"Corri Davidou"},
   {"id":3, "name":"Alyse Dadson"},
   {"id":4, "name":"Orelle Roughey"},
   {"id":5, "name":"Jaquith Wealthall"},
   {"id":6, "name":"Boothe Martinson"},
   {"id":7, "name":"Patten Bedell"},
   {"id":8, "name":"Danita Pilipyak"},
   {"id":9, "name":"Harlene Branigan"},
   {"id":10, "name":"Boothe Martinson"}
]

Code Execution:

@Inject
People people;

Page<Person> page = people.findAll(Pageable.ofPage(1).size(2).sortBy(Sort.asc("id")));

Resulting Page Content:

[
   {"id":1, "name":"Lin Le Marchant"},
   {"id":2, "name":"Corri Davidou"}
]

Next Page Execution:

Pageable nextPageable = page.nextPageable();
Page<Person> page2 = people.findAll(nextPageable);

Resulting Page Content:

[
   {"id":3, "name":"Alyse Dadson"},
   {"id":4, "name":"Orelle Roughey"}
]

In this scenario, each page represents a subset of the dataset, and developers can navigate through the pages efficiently using offset pagination.

Offset pagination is a valuable tool for Java developers when dealing with large datasets, providing control, efficiency, and a seamless user experience.

4.8.2. Keyset Pagination

Keyset pagination aims to reduce missed and duplicate results across pages by querying relative to the observed values of entity properties that constitute the sorting criteria. Keyset pagination can also offer an improvement in performance because it avoids fetching and ordering results from prior pages by causing those results to be non-matching. A Jakarta Data provider appends additional conditions to the query and tracks keyset values automatically when KeysetAwareSlice or KeysetAwarePage are used as the repository method return type. The application invokes nextPageable or previousPageable on the keyset aware slice or page to obtain a Pageable which keeps track of the keyset values.

For example,

@Repository
public interface CustomerRepository extends BasicRepository<Customer, Long> {
  KeysetAwareSlice<Customer> findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(
                                 int zipcode, Pageable pageable);
}

You can obtain the initial page relative to an offset and subsequent pages relative to the last entity of the current page as follows,

for (Pageable p = Pageable.ofSize(50); p != null; ) {
  page = customers.findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(55901, p);
  ...
  p = page.nextPageable();
}

Or you can obtain the next (or previous) page relative to a known entity,

Customer c = ...
Pageable p = Pageable.ofSize(50).afterKeyset(c.lastName, c.firstName, c.id);
page = customers.findByZipcodeOrderByLastNameAscFirstNameAscIdAsc(55902, p);

The sort criteria for a repository method that performs keyset pagination must uniquely identify each entity and must be provided by:

  • OrderBy name pattern of the repository method (as in the examples above) or @OrderBy annotation(s) on the repository method.

  • Sort parameters of the Pageable that is supplied to the repository method.

4.8.2.1. Example of Appending to Queries for Keyset Pagination

Without keyset pagination, a Jakarta Data provider that is based on Jakarta Persistence might compose the following JPQL for the findByZipcodeOrderByLastNameAscFirstNameAscIdAsc repository method from the prior example:

SELECT o FROM Customer o WHERE (o.zipCode = ?1)
                         ORDER BY o.lastName ASC, o.firstName ASC, o.id ASC

When keyset pagination is used, the keyset values from the Cursor of the Pageable are available as query parameters, allowing the Jakarta Data provider to append additional query conditions. For example,

SELECT o FROM Customer o WHERE (o.zipCode = ?1)
                           AND (   (o.lastName > ?2)
                                OR (o.lastName = ?2 AND o.firstName > ?3)
                                OR (o.lastName = ?2 AND o.firstName = ?3 AND o.id > ?4)
                               )
                         ORDER BY o.lastName ASC, o.firstName ASC, o.id ASC
4.8.2.2. Avoiding Missed and Duplicate Results

Because searching for the next page of results is relative to a last known position, it is possible with keyset pagination to allow some types of updates to data while pages are being traversed without causing missed results or duplicates to appear. If you add entities to a prior position in the traversal of pages, the shift forward of numerical position of existing entities will not cause duplicates entities to appear in your continued traversal of subsequent pages because keyset pagination does not query based on a numerical position. If you remove entities from a prior position in the traversal of pages, the shift backward of numerical position of existing entities will not cause missed entities in your continued traversal of subsequent pages because keyset pagination does not query based on a numerical position.

Other types of updates to data, however, will cause duplicate or missed results. If you modify entity properties which are used as the sort criteria, keyset pagination cannot prevent the same entity from appearing again or never appearing due to the altered values. If you add an entity that you previously removed, whether with different values or the same values, keyset pagination cannot prevent the entity from being missed or possibly appearing a second time due to its changed values.

4.8.2.3. Restrictions on use of Keyset Pagination
  • The repository method signature must return KeysetAwareSlice or KeysetAwarePage. A repository method with return type of KeysetAwareSlice or KeysetAwarePage must raise UnsupportedOperationException if the database is incapable of keyset pagination.

  • The repository method signature must accept a Pageable parameter.

  • Sort criteria must be provided and should be minimal.

  • The combination of provided sort criteria must uniquely identify each entity such that the sort criteria defines a deterministic ordering of entities.

  • The entities within each page must be ordered according to the provided sort criteria.

  • Page numbers for keyset pagination are estimated relative to prior page requests or the observed absence of further results and are not accurate. Page numbers must not be relied upon when using keyset pagination.

  • Page totals and result totals are not accurate for keyset pagination and must not be relied upon.

  • A next or previous page can end up being empty. You cannot obtain a next or previous Pageable from an empty page because there are no keyset values relative to which to query.

  • A repository method that is annotated with @Query and performs keyset pagination must omit the ORDER BY clause from the provided query and instead must supply the sort criteria via @OrderBy annotations or Sort parameters of Pageable. The provided query must end with a WHERE clause to which additional conditions can be appended by the Jakarta Data provider. The Jakarta Data provider is not expected to parse query text that is provided by the application.

4.8.2.4. Keyset Pagination Example with Sorts

Here is an example where an application uses @Query to provide a partial query to which the Jakarta Data provider can generate and append additional query conditions and an ORDER BY clause.

@Repository
public interface CustomerRepository extends BasicRepository<Customer, Long> {
  @Query("SELECT o FROM Customer o WHERE (o.totalSpent / o.totalPurchases > ?1)")
  KeysetAwareSlice<Customer> withAveragePurchaseAbove(float minimum, Pageable pagination);
}

Example traversal of pages:

for (Pageable p = Pageable.ofSize(25).sortBy(Sort.desc("yearBorn"),
                                             Sort.asc("name"),
                                             Sort.asc("id")));
     p != null; ) {
  page = customers.withAveragePurchaseAbove(50.0f, p);
  ...
  p = page.nextPageable();
}
4.8.2.5. Example with Before/After Cursor

In this example, the application uses a cursor to request pages in forward and previous direction from a specific value, which is the price for a matching product.

@Repository
public interface Products extends CrudRepository<Product, Long> {
  KeysetAwareSlice<Product> findByNameContains(String namePattern, Pageable pageRequest);
}

Obtaining the next 10 products that cost $50.00 or more:

float priceMidpoint = 50.0f;
Pageable pageRequest = Pageable.ofSize(10)
                               .sortBy(Sort.asc("price"), Sort.asc("id"))
                               .afterKeyset(priceMidpoint, 0L);
KeysetAwareSlice<Product> moreExpensive = products.findByNameContains(pattern, pageRequest);

Obtaining the previous 10 products:

if (moreExpensive.hasContent())
    pageRequest = pageRequest.beforeKeysetCursor(moreExpensive.getKeysetCursor(0));
else
    pageRequest = pageRequest.beforeKeyset(priceMidpoint, 1L);
KeysetAwareSlice<Product> lessExpensive = products.findByNameContains(pattern, pageRequest);
4.8.2.6. Scenario: Person Entity and People Repository

This keyset cursor-based pagination scenario uses the same Person entity and example dataset from the offset-based pagination scenario, but orders it by name and then by id,

[
   {"id":3, "name":"Alyse Dadson"},
   {"id":6, "name":"Boothe Martinson"},
   {"id":10, "name":"Boothe Martinson"},
   {"id":2, "name":"Corri Davidou"},
   {"id":8, "name":"Danita Pilipyak"},
   {"id":9, "name":"Harlene Branigan"},
   {"id":5, "name":"Jaquith Wealthall"},
   {"id":1, "name":"Lin Le Marchant"},
   {"id":4, "name":"Orelle Roughey"},
   {"id":7, "name":"Patten Bedell"}
]
@Repository
public interface People extends BasicRepository<Person, Long> {
    KeysetAwarePage<Person> findAll(Pageable pagination);
}

Code Execution:

@Inject
People people;

Pageable firstPageRequest = Pageable.ofSize(4).sortBy(Sort.asc("name"), Sort.asc("id"));
KeysetAwarePage<Person> page = people.findAll(firstPageRequest);

Resulting Page Content:

[
   {"id":3, "name":"Alyse Dadson"},
   {"id":6, "name":"Boothe Martinson"},
   {"id":10, "name":"Boothe Martinson"},
   {"id":2, "name":"Corri Davidou"}
]

Deletion of an Entity:

// The user decides to remove one of the entities that has the same name,
people.deleteById(10);

Next Page Execution:

Pageable nextPageRequest = page.nextPageable();
KeysetAwarePage<Person> page2 = people.findAll(nextPageRequest);

Resulting Page Content:

[
   {"id":8, "name":"Danita Pilipyak"},
   {"id":9, "name":"Harlene Branigan"},
   {"id":5, "name":"Jaquith Wealthall"},
   {"id":1, "name":"Lin Le Marchant"}
]

It should be noted, the above result is different than what would be retrieved with offset-based pagination, where the removal of an entity from the first page shifts the offset for entries 5 through 8 to start from {"id":9, "name":"Harlene Branigan"}, skipping over {"id":8, "name":"Danita Pilipyak"} that becomes offset position 4 after the removal. Keyset cursor-based pagination does not skip the entity because it queries relative to a cursor position, starting from the next entity after {"id":2, "name":"Corri Davidou"}.

5. Jakarta Data Providers

Jakarta Data providers implement repository interfaces, performing queries and other operations related to entities per the rules of the Jakarta Data specification. A Jakarta Data provider makes the repository implementation available to the application via dependency injection. The Jakarta Data specification defines the rules by which Jakarta Data providers must abide to ensure that multiple Jakarta Data providers are able to coexist in a system without interfering or overlapping on the same injection points.

5.1. Provider support for Entities

A Jakarta Data provider supplies an implementation of repository interfaces in Jakarta Data for one or more types of entities. An entity refers to a class that represents objects in a storage engine, such as SQL or NoSQL databases.

The jakarta.persistence.Entity annotation from the Jakarta Persistence specification can be used by repository entity classes for Jakarta Data providers that are backed by a Jakarta Persistence provider. Other Jakarta Data providers must not support the jakarta.persistence.Entity annotation.

The jakarta.nosql.Entity annotation from the Jakarta NoSQL specification can be used by repository entity classes for Jakarta Data providers that are backed by NoSQL databases. Other Jakarta Data providers must not support the jakarta.nosql.Entity annotation.

Jakarta Data providers that define custom entity annotations must follow the convention that the class name of all supported entity annotation types ends with Entity. This enables Jakarta Data providers to identify if a repository entity class contains entity annotations from different Jakarta Data providers so that the corresponding Repository can be ignored by Jakarta Data providers that should not provide it.

Jakarta Data providers must ignore all Repository annotations where annotations for the corresponding entity are available at run time and none of the entity annotations are supported by the Jakarta Data provider. Ignoring these Repository annotations allows other Jakarta Data providers to handle them.

5.2. Provider Name

The entity annotation class is the primary strategy to avoid conflicts between Jakarta Data providers. In most cases, it is sufficient to differentiate between providers. In situations where multiple Jakarta Data providers support the same entity annotation class, the application can specify the name of the desired Jakarta Data provider using the optional provider attribute of the Repository annotation.

To ensure compatibility and prevent conflicts, Jakarta Data providers must disregard all Repository annotations that specify a different provider’s name through the Repository.provider() attribute. By ignoring these annotations, Jakarta Data providers allow other Jakarta Data providers to handle them.

6. Interoperability with other Jakarta EE Specifications

This section discusses interoperability with related Jakarta EE specifications. When operating within a Jakarta EE product, the availability of other Jakarta EE technologies depends on whether the Jakarta EE Core profile, Jakarta EE Web profile, or Jakarta EE Platform is used.

6.1. Jakarta Contexts and Dependency Injection

Contexts and Dependency Injection (CDI) is a specification in the Jakarta EE Core profile that provides a powerful and flexible dependency injection framework for Java applications. CDI lets you decouple components and manage their lifecycle through dependency injection, enabling loose coupling and promoting modular and reusable code.

In Jakarta EE products, CDI provides the ability to register implementations of Jakarta Data repositories for injection into applications via the the @Inject annotation. The following example illustrates this integration:

@Repository
public interface CarRepository extends BasicRepository<Car, Long> {

  List<Car> findByType(CarType type);

  Optional<Car> findByName(String name);

}

In the above example, a CarRepository interface extends the BasicRepository interface provided by Jakarta Data. The BasicRepository interface offers a set of basic operations for entities.

By annotating the CarRepository interface with @Repository, the application requests that the Jakarta Data provider generate an implementation of the interface and its methods. To make the implementation available to the application, the Jakarta Data provider uses CDI to register the implementation as a bean or registers a producer or producer factory to provide the implementation on unqualified injection points for the CarRepository interface.

With CDI and the @Inject annotation, the application is able to inject the CarRepository instance and utilize its methods:

@Inject
CarRepository repository;

// ...

List<Car> cars = repository.findByType(CarType.SPORT);

In the above snippet, the application injects the CarRepository instance using the @Inject annotation. Once injected, the repository object can be used to invoke various data access methods defined by the CarRepository interface, such as save(), findByType(), and findByName(). This integration between CDI and Jakarta Data allows for seamless management of repository instances within Jakarta EE applications.

6.1.1. CDI Extensions for Jakarta Data providers

In environments where CDI Full or CDI Lite is available, Jakarta Data providers can make use of CDI extensions - jakarta.enterprise.inject.spi.Extension and jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension - to discover interfaces that are annotated with @Repository and provide their implementations to be injected into injection points within the application. Jakarta Data does not mandate the use of a specific type of CDI extension but places the general requirement on the Jakarta Data provider to arrange for injection of the provided repository implementation into injection points having a type that is the repository interface and having no qualifiers (other than Default or Any).

CDI Lite (corresponding to Jakarta Core profile) does not include a requirement to support jakarta.enterprise.inject.spi.Extension, which is part of CDI Full (Jakarta Web profile and Jakarta Platform). The jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension applies to both CDI Lite and CDI Full.
Jakarta Data providers that wish to provide both extensions can use CDI’s @SkipIfPortableExtensionPresent to prevent the BuildCompatibleExtension from colliding with the portable Extension when running in the Jakarta Web Profile or Jakarta Platform where CDI Full is present.

6.2. Jakarta Transactions Usage

When running in an environment where Jakarta Transactions is available and a global transaction is active on the thread of execution for a repository operation and the data source backing the repository is capable of transaction enlistment, the repository operation enlists the data source resource as a participant in the transaction. The repository operation does not commit or roll back the transaction that was already present on the thread, but it might cause the transaction to be marked as 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, a repository method can be annotated with the jakarta.transaction.Transactional annotation, which is applied to the execution of the repository method.

6.3. Interceptor Annotations on Repository Methods

When a repository method is annotated with an interceptor binding annotation, the interceptor is bound to the repository bean according to the interceptor binding annotation of the repository interface method, causing the bound interceptor to be invoked around the repository method when it runs. This enables the use of interceptors such as jakarta.transaction.Transactional on repository methods when running in an environment where the Jakarta EE technology that provides the interceptor is available.

6.4. Jakarta Persistence

When integrating Jakarta Data with Jakarta Persistence, developers can leverage the JPA annotations to define the mapping of entities in repositories. Entities in Jakarta Persistence are typically annotated with jakarta.persistence.Entity to indicate their persistence capability.

A Jakarta Data provider that supports Jakarta Persistence allows you to define repositories for classes marked with the jakarta.persistence.Entity annotation.

By supporting Jakarta Persistence annotations, Jakarta Data providers enable Java developers to utilize familiar and standardized mapping techniques when defining entities in repositories, ensuring compatibility and interoperability with the respective technologies.

6.4.1. Persistence Context

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. All entities that are returned by repository methods must be in a detached state such that modifications to these entities are not persisted to the database unless the application explicitly invokes a Save or Update life cycle method for the entity.

6.5. Jakarta NoSQL

When integrating Jakarta Data with Jakarta NoSQL, developers can use the NoSQL annotations to define the mapping of entities in repositories. Entities in Jakarta NoSQL are typically annotated with jakarta.nosql.Entity to indicate their suitability for persistence in NoSQL databases.

A Jakarta Data provider that supports Jakarta NoSQL will scan classes marked with the jakarta.nosql.Entity annotation.

By supporting Jakarta NoSQL annotations, Jakarta Data providers enable Java developers to utilize familiar and standardized mapping techniques when defining entities in repositories, ensuring compatibility and interoperability with the respective technologies.

6.6. Jakarta Bean Validation

Integrating with Jakarta Validation ensures data consistency within the Java layer. By applying validation rules to the data, developers can enforce constraints and business rules, preventing invalid or inconsistent information from being processed or persisted.

Using Jakarta Validation brings several advantages. It helps maintain data integrity, improves data quality, and enhances the reliability of the application. Catching validation errors early in the Java layer can identify and resolve potential issues before further processing or persistence occurs. Additionally, Jakarta Validation allows for declarative validation rules, simplifying the validation logic and promoting cleaner and more maintainable code.

In Jakarta Data, repository methods participate in method validation as defined by the section "Method and constructor validation" of the Jakarta Validation specification. Method validation includes validation of constraints on method parameters and results. The jakarta.validation.Valid annotation is used to opt in to cascading validation that validates constraints that are found on an object that is supplied as a parameter or returned as a result.

The following code snippet demonstrates the usage of Jakarta Validation annotations in the Student entity class:

@Entity
public class Student {

    @Id
    private String id;

    @Column
    @NotBlank
    private String name;

    @Positive
    @Min(18)
    @Column
    private int age;
}

In this example, the name field is annotated with @NotBlank, indicating that it must not be blank. The age field is annotated with both @Positive and @Min(18), ensuring it is a positive integer greater than or equal to 18.

The School repository interface, shown below, uses the jakarta.validation.Valid annotation to cause the constraints from the Student entity to be validated during the save operation, whereas the validation constraints are not applied to the Student entities returned as a result of the findByAgeLessThanEqual operation because the findByAgeLessThanEqual method does not include a jakarta.validation.Valid annotation that applies to the return value.

@Repository
public interface School extends DataRepository<Student, String> {
    @Save
    void save(@Valid Student s);

    List<Student> findByAgeLessThanEqual(@Min(18) int age);
}

6.6.1. Avoiding Overlap with Validation from Jakarta Persistence

Jakarta Data providers that are built using Jakarta Persistence might require the user to define persistence units for repositories or might handle the details of defining the persistence units internally. A user that defines the persistence unit for a Jakarta Data repository must specify the validation-mode as NONE per the "Enabling Automatic Validation" section of the Jakarta Persistence specification to avoid duplicate validation of entities. Similarly, the Jakarta Data provider must specify either the validation-mode of NONE or the jakarta.persistence.validation.mode map key with value of none that is defined in the "Enabling Automatic Validation" section of the Jakarta Persistence specification to avoid duplicate validation of entities.

7. Portability in Jakarta Data

Jakarta Data offers varying degrees of portability depending on the database and capabilities used. A subset of function is standardized across all database types, while other subsets of function are standardized only for the specific types of databases to which the capability pertains. These requirements are explicitly called out in documentation throughout the specification, such as the "Unavailable In" column of the tables of repository keywords in the Jakarta Data module JavaDoc. The Jakarta Data specification levies requirements against applications and Jakarta Data providers, but not against databases. The Jakarta Data specification requires the Jakarta Data provider to offer function to the extent that the database is capable and to raise an exception where the database is not capable. It is not the aim of Jakarta Data to offer the ability to switch between different databases, but to standardize a common starting point for data access from which capability that is specific to the various types of databases is able to build upon.

The portability that is offered by Jakarta Data pertains to usage of the Jakarta Data API by application code, enabling application code that restricts itself to the Jakarta Data API to remain the same when used with any Jakarta Data provider running against the same database. Jakarta Data relies on external persistence specifications such as Jakarta Persistence and Jakarta NoSQL to standardize entity models. Jakarta Data does not place any requirements on the format of data to make persisted data and other database artifacts portable across providers. Jakarta Data does not offer any means to migrate data that is persisted by one provider to a form that is usable by another provider.

7.1. Portability for Relational Databases

All functionality defined by Jakarta Data must be supported when using relational databases.

7.1.1. Jakarta Persistence Annotations

Jakarta Data, when used in conjunction with a Jakarta Data provider that is backed by Jakarta Persistence, requires support for the jakarta.persistence.Entity annotation on entity classes and requires support for the following minimal set of Jakarta Persistence annotations for entity properties:

  • jakarta.persistence.Basic

  • jakarta.persistence.Column

  • jakarta.persistence.Convert

  • jakarta.persistence.ElementCollection

  • jakarta.persistence.Embedded

  • jakarta.persistence.Enumerated

  • jakarta.persistence.GeneratedValue

  • jakarta.persistence.Id

  • jakarta.persistence.Temporal

  • jakarta.persistence.Version

Jakarta Data providers might choose to offer additional functionality that is provided by Jakarta Persistence beyond what is stated in this section.

7.1.2. Built-In Repositories

Jakarta Data’s built-in repositories, such as PageableRepository and BasicRepository, are designed to offer consistent and well-defined methods compatible with relational databases. Developers can rely on these repositories to perform common data access tasks.

7.1.3. Query Methods

Jakarta Data’s support for query methods, including pagination, ordering, and limiting, is designed to work seamlessly with relational databases.

All limitations of Jakarta Persistence, the respective Jakarta Persistence provider, JPQL, SQL, and the database apply when using entities that are annotated with jakarta.persistence.Entity. Repository methods must correspond to operations that are valid via JPQL and SQL queries. For example, although one can write a repository method that asks for sorting by a collection attribute or attempts to perform a Like operation on a numeric type rather than a String, there is no expectation for a Jakarta Data provider to support doing so because Jakarta Persistence, JPQL, and SQL do not offer the ability.

By aligning Jakarta Data closely with relational databases, developers can expect high portability and compatibility. This approach ensures that Jakarta Data remains a powerful tool for simplifying data access, irrespective of the specific relational database used.

7.2. Portability for NoSQL Databases

Portability in Jakarta Data extends to various NoSQL databases, each presenting unique challenges and capabilities. Jakarta Data aims to provide a consistent experience across these NoSQL database types. This section covers the key portability aspects for four categories of NoSQL databases: key-value, wide-column, document, and graph databases.

7.2.1. Key-Value Databases

Key-value databases resemble dictionaries or Maps in Java, where data is primarily accessed using a key. In such databases, queries unrelated to keys are typically limited. To ensure a minimum level of support, Jakarta Data mandates the implementation of BasicRepository built-in methods that require an identifier or a key. However, the deleteAll and count methods are not required. Methods relying on complex queries, which are defined as queries that do not use the Key or identifier, raise java.lang.UnsupportedOperationException due to the fundamental nature of key-value databases.

For any NoSQL database type not covered here, such as time series databases, the Key-value support serves as the minimum required level of compatibility.

7.2.2. Wide-Column Databases

Wide-column databases offer more query flexibility, even allowing the use of secondary indexes, albeit potentially impacting performance. When interacting with wide-column databases, Jakarta Data requires the implementation of the BasicRepository along with all of its methods, including Query by Method Name. However, developers should be mindful that certain query keywords, such as "And" or "Or," may not be universally supported in these databases. The full set of required keywords is documented in the section of the Jakarta Data module JavaDoc that is titled "Reserved Keywords for Query by Method Name".

7.2.3. Document Databases

Document databases provide query flexibility akin to relational databases, offering robust query capabilities. They encourage denormalization for performance optimization. When interfacing with document databases, Jakarta Data goes a step further by supporting both built-in repositories: BasicRepository and PageableRepository. Additionally, Query by Method Name is available, though developers should be aware that some keywords may not be universally supported. The full set of required keywords is documented in the section of the Jakarta Data module JavaDoc that is titled "Reserved Keywords for Query by Method Name".

These portability considerations reflect Jakarta Data’s commitment to providing a consistent data access experience across diverse NoSQL database types. While specific capabilities and query support may vary, Jakarta Data aims to simplify data access, promoting flexibility and compatibility in NoSQL database interactions.

7.2.4. Graph Databases

A Graph database, a specialized NoSQL variant, excels in managing intricate data relationships, rivaling traditional relational databases. Its unique strength lies in its ability to handle both directed and undirected edges (or relationships) between vertices (or nodes) and store properties on both vertices and edges.

Graph databases excel at answering queries that return rows containing flat objects, collections, or a combination of flat objects and connections. However, portability is only guaranteed when mapping rows to classes, and when queries specified via annotations or other supported means are used. It should be noted that queries derived from keywords and combinations of mapped classes/properties will be translated into vendor-specific queries.

It is important to note that in Jakarta Data the Graph database supports the built-in repositories: BasicRepository and PageableRepository. Additionally, Query by Method Name is available, though developers should be aware that some keywords may not be universally supported. The full set of required keywords is documented in the section of the Jakarta Data module JavaDoc that is titled "Reserved Keywords for Query by Method Name".