Using Jakarta Data Repositories

Using Jakarta Data Repositories

This chapter describes how to use Jakarta Data repositories in Eclipse GlassFish applications. Jakarta Data provides a standard API for data access that simplifies database operations through repository interfaces and automatic query generation.

The following topics are addressed here:

For more information about Jakarta Data 1.0.1, see the official Jakarta Data 1.0.1 documentation.

Overview of Jakarta Data

Jakarta Data is a specification that provides a standard API for data access in Jakarta EE applications. It offers a repository-based programming model that reduces boilerplate code and provides type-safe database operations.

Key features of Jakarta Data include:

  • Repository interfaces with automatic implementation generation

  • Query derivation from method names and annotations

  • Support for custom queries using JDQL (Jakarta Data Query Language) or JPQL

  • Built-in pagination and sorting capabilities

  • Integration with Jakarta Persistence (JPA) for relational databases

  • Integration with Jakarta NoSQL for NoSQL databases

  • Type-safe query building

  • Support for both SQL and NoSQL databases in the same application

Jakarta Data repositories work by defining interfaces that may extend base repository types. The Jakarta Data provider automatically generates implementations of these interfaces at build time or runtime.

In Eclipse GlassFish, Jakarta Data is implemented using Eclipse JNoSQL, which provides:

  • Unified API - The same repository interfaces for both SQL and NoSQL databases

  • Multi-database support - Support for SQL, Document, Key-Value, Wide-Column, and Graph databases

  • JPA integration - Seamless integration with existing JPA entities for relational databases

  • NoSQL entities - Support for Jakarta NoSQL entities for NoSQL databases

  • Automatic routing - Automatic database detection and query creation based on entity type

Choosing Between JPA and NoSQL

When deciding between JPA entities and NoSQL entities for your Jakarta Data repositories, consider the following factors:

Factor JPA Entities NoSQL Entities

Database Type

Relational databases (PostgreSQL, MySQL, Oracle, etc.)

NoSQL databases (MongoDB, Redis, Cassandra, Neo4j, etc.)

Data Structure

Structured data with relationships

Flexible schemas, nested documents, key-value pairs

Transactions

Full JTA transaction support

Limited transaction support (database-dependent)

Validation

Jakarta Validation supported

Manual validation required

Relationships

Full relationship mapping (@OneToMany, @ManyToOne, etc.)

No relationship mapping (embed or reference manually)

Query Language

JPQL and JDQL

JDQL only

Schema Evolution

Requires database migrations

Flexible schema changes

Scalability

Vertical scaling (with some horizontal options)

Horizontal scaling

ACID Properties

Full ACID compliance

Eventual consistency (varies by database)

Use JPA entities when:

  • You need strong consistency and ACID transactions

  • Your data has complex relationships

  • You require Jakarta Validation

  • You’re working with existing relational database schemas

Use NoSQL entities when:

  • You need flexible schemas and rapid development

  • You’re working with large-scale, distributed data

  • Your data is document-oriented or graph-based

  • You need horizontal scalability

Basic Repository Example

This example shows a simple repository interface that extends CrudRepository for a Product JPA entity. It demonstrates both the modern annotated approach and the deprecated method name-based approach.

JPA Entity Example

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    @NotBlank(message = "Product code is required")
    @Size(max = 20, message = "Product code must not exceed 20 characters")
    private String code;

    @NotBlank(message = "Product name is required")
    @Size(max = 100, message = "Product name must not exceed 100 characters")
    private String name;

    private String category;

    @NotNull(message = "Price is required")
    @DecimalMin(value = "0.0", inclusive = false, message = "Price must be greater than 0")
    private BigDecimal price;

    // Constructors, getters, and setters
}
import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Repository;
import jakarta.data.repository.Find;
import jakarta.data.repository.By;
import jakarta.data.repository.OrderBy;
import jakarta.data.repository.Query;

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

    // Annotated queries using @Find and @By annotations (RECOMMENDED)
    @Find
    List<Product> findProducts(@By("name") String name);

    @Find
    Optional<Product> findProduct(@By("code") String code);

    @Find
    @OrderBy("price")
    List<Product> findProducts(@By("category") String category);

    // Custom query using JDQL
    @Query("WHERE price BETWEEN :minPrice AND :maxPrice ORDER BY price")
    List<Product> findProductsByPriceRange(BigDecimal minPrice, BigDecimal maxPrice);
}

Defining Repository Interfaces

Jakarta Data repositories are defined as interfaces annotated with @Repository annotation (@jakarta.data.Repository). The interface can extend one of the base repository interfaces, which provide ready-to-use methods:

  • Repository<T, K> - Marker interface with no predefined methods

  • CrudRepository<T, K> - Adds basic CRUD operations (save, findById, findAll, delete, etc.)

  • PageableRepository<T, K> - Extends CrudRepository and adds pagination support

Query Methods

Jakarta Data supports automatic query generation using annotated methods with query annotations like @Find, @Count, @Exists, and @Delete combined with parameter annotations like @By. This is the recommended approach for defining repository methods.

Supported Query Annotations

  • @Find - Find operations that return entities

  • @Count - Count operations that return the number of matching entities

  • @Exists - Existence checks that return boolean values

  • @Delete - Delete operations that remove entities

  • @Insert - Insert operations that create new entities

  • @Update - Update operations that modify existing entities

  • @Save - Save operations that insert or update entities

Parameter Annotations

  • @By("propertyName") - Specifies the entity property to query by. Can be nested to specify properties of referenced entities (e.g., @By("customer.address.city")).

If the application is compiled with parameter names preserved in bytecode (e.g., with javac -parameters), @By is not required. The property name will be derived from the parameter name if the @By annotation is not present.

Annotated Query Examples

import jakarta.data.repository.*;

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {

    // Find by single property
    @Find
    List<Customer> findCustomers(@By("lastName") String lastName);

    // Multiple conditions (implicit AND)
    @Find
    List<Customer> findCustomers(@By("firstName") String firstName, @By("lastName") String lastName);

    // Nested property access
    @Find
    List<Customer> findCustomers(@By("address.city") String city);

    // Counting
    @Count
    long countCustomers(@By("status") String status);

    // Existence checks
    @Exists
    boolean customerExists(@By("email") String email);

    // Delete operations
    @Delete
    void deleteCustomers(@By("status") String status);

    // Insert operations
    @Insert
    Customer insertCustomer(Customer customer);

    @Insert
    List<Customer> insertCustomers(List<Customer> customers);

    // Update operations
    @Update
    Customer updateCustomer(Customer customer);

    // Save operations (insert or update)
    @Save
    Customer saveCustomer(Customer customer);

    // Null value handling
    @Find
    List<Customer> findCustomers(@By("middleName") String middleName);

    default List<Customer> findCustomersWithNullMiddleName() {
        return findCustomers((String) null);
    }
}

Custom Queries

For complex queries that cannot be expressed through annotated methods, Jakarta Data supports custom queries using the @Query annotation. Jakarta Data specifies its own query language called JDQL (Jakarta Data Query Language), which is similar to JPQL (Java Persistence Query Language). Eclipse GlassFish also supports full JPQL queries for JPA entities.

JDQL vs JPQL

JDQL (Jakarta Data Query Language):

  • Simplified syntax that allows omitting the SELECT clause

  • Automatically infers the entity type from the method return type

  • Works with both JPA and NoSQL entities

  • Example: WHERE customer.id = :customerId AND status = :status

JPQL (Java Persistence Query Language):

  • Full SQL-like syntax with explicit SELECT clauses

  • Only works with JPA entities

  • More powerful for complex joins and projections

  • Example: SELECT o FROM Order o WHERE o.customer.id = :customerId AND o.status = :status

Custom Query Examples

import jakarta.data.repository.Query;

@Repository
public interface OrderRepository extends CrudRepository<Order, Long> {

    // JDQL query - simplified syntax
    @Query("WHERE customer.id = :customerId AND status = :status")
    List<Order> findOrdersByCustomerAndStatus(Long customerId, String status);

    // JDQL query with range conditions
    @Query("WHERE orderDate BETWEEN :startDate AND :endDate ORDER BY orderDate DESC")
    List<Order> findOrdersByDateRange(LocalDate startDate, LocalDate endDate);

    // JDQL query with pattern matching
    @Query("WHERE customer.email LIKE :pattern")
    List<Order> findOrdersByCustomerEmailPattern(String pattern);

    // JDQL query with collection operations
    @Query("WHERE status IN :statuses")
    List<Order> findOrdersByStatuses(Collection<String> statuses);

    // JPQL query with explicit SELECT and JOIN (JPA entities only)
    @Query("SELECT o FROM Order o JOIN o.items i WHERE i.product.category = :category")
    List<Order> findOrdersWithProductCategory(String category);

    // JPQL query with aggregation (JPA entities only)
    @Query("SELECT COUNT(o) FROM Order o WHERE o.orderDate >= :startDate")
    long countOrdersSince(LocalDate startDate);

    // JPQL query with projection (JPA entities only)
    @Query("SELECT NEW com.example.dto.OrderSummary(o.id, o.customer.name, o.total) FROM Order o WHERE o.status = :status")
    List<OrderSummary> findOrderSummariesByStatus(String status);
}

Method Name-Based Queries (Deprecated)

Method name-based query derivation is deprecated and may be removed in future versions. It’s provided for migrating existing code to Data repositories. For new code, use the annotated approach shown in the previous sections instead.

Eclipse GlassFish also supports method name-based query derivation using a standardized naming convention. While this approach is deprecated, it’s still supported for backward compatibility and migration purposes.

Supported Keywords

  • findBy, getBy, queryBy, readBy - Find operations

  • countBy - Count operations

  • existsBy - Existence checks

  • deleteBy, removeBy - Delete operations

Property Expressions

  • And, Or - Logical operators

  • Between - Range queries

  • LessThan, GreaterThan, LessThanEqual, GreaterThanEqual - Comparison operators

  • Like, NotLike - Pattern matching

  • In, NotIn - Collection membership

  • IsNull, IsNotNull - Null checks

  • True, False - Boolean values

  • OrderBy - Sorting (can be combined with Asc or Desc)

Method Name-Based Query Examples

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {

    // Find by single property
    List<Customer> findByLastName(String lastName);

    // Multiple conditions with And
    List<Customer> findByFirstNameAndLastName(String firstName, String lastName);

    // Or conditions
    List<Customer> findByFirstNameOrLastName(String name);

    // Comparison operators
    List<Customer> findByAgeGreaterThan(int age);
    List<Customer> findByAgeLessThan(int age);
    List<Customer> findByAgeGreaterThanEqual(int age);
    List<Customer> findByAgeLessThanEqual(int age);
    List<Customer> findByAgeBetween(int minAge, int maxAge);

    // Pattern matching
    List<Customer> findByEmailLike(String pattern);
    List<Customer> findByEmailNotLike(String pattern);

    // Collection operations
    List<Customer> findByStatusIn(Collection<String> statuses);
    List<Customer> findByStatusNotIn(Collection<String> statuses);

    // Null checks
    List<Customer> findByMiddleNameIsNull();
    List<Customer> findByMiddleNameIsNotNull();

    // Boolean values
    List<Customer> findByActiveTrue();
    List<Customer> findByActiveFalse();

    // Sorting
    List<Customer> findByLastNameOrderByFirstNameAsc(String lastName);
    List<Customer> findByLastNameOrderByFirstNameDesc(String lastName);
    List<Customer> findByStatusOrderByLastNameAscFirstNameAsc(String status);

    // Counting
    long countByStatus(String status);
    long countByAgeGreaterThan(int age);

    // Existence checks
    boolean existsByEmail(String email);
    boolean existsByLastNameAndFirstName(String lastName, String firstName);

    // Delete operations
    void deleteByStatus(String status);
    long removeByAgeGreaterThan(int age);

    // Complex combinations
    List<Customer> findByLastNameAndAgeGreaterThanAndActiveTrue(String lastName, int age);
    List<Customer> findByEmailLikeOrPhoneLike(String emailPattern, String phonePattern);
}

Nested Property Access

Method names can also access nested properties using camel case:

@Repository
public interface OrderRepository extends CrudRepository<Order, Long> {

    // Access nested properties
    List<Order> findByCustomerLastName(String lastName);
    List<Order> findByCustomerAddressCity(String city);
    List<Order> findByCustomerAddressCountry(String country);

    // Complex nested queries
    List<Order> findByCustomerLastNameAndShippingAddressCity(String lastName, String city);
    List<Order> findByCustomerActiveAndOrderDateGreaterThan(boolean active, LocalDate date);
}

Pagination and Sorting

Jakarta Data provides built-in support for pagination and sorting through the Pageable and Sort interfaces.

Using Pageable

import jakarta.data.repository.Pageable;
import jakarta.data.repository.Page;
import jakarta.data.repository.Sort;
import jakarta.data.repository.Find;
import jakarta.data.repository.By;

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

    @Find
    Page<Product> findProducts(@By("category") String category, Pageable pageable);

    @Query("WHERE price > :price")
    List<Product> findProductsAbovePrice(BigDecimal price, Sort sort);
}

Usage Example

@Inject
private ProductRepository productRepository;

public void demonstratePagination() {
    // Create pageable request for page 0, size 10, sorted by name
    Pageable pageable = Pageable.of(0, 10, Sort.by("name").ascending());

    Page<Product> page = productRepository.findProducts("Electronics", pageable);

    List<Product> products = page.getContent();
    long totalElements = page.getTotalElements();
    int totalPages = page.getTotalPages();
    boolean hasNext = page.hasNext();
}

Configuring JPA Data Repositories

To use Jakarta Data repositories with JPA entities in your Eclipse GlassFish application, you need to:

  1. Add Jakarta Data API dependency to your project

  2. Configure a Jakarta Persistence persistence unit

  3. Set up database connection (optional - uses default Derby database if not specified)

Adding Dependencies

Add the Jakarta Data API dependency to your pom.xml:

<dependency>
    <groupId>jakarta.data</groupId>
    <artifactId>jakarta.data-api</artifactId>
    <version>1.0.1</version>
    <scope>provided</scope>
</dependency>

Alternatively, if you’re using the full Jakarta EE platform:

<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>11.0.0</version>
    <scope>provided</scope>
</dependency>

Jakarta Persistence Configuration

Configure your persistence unit in persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
    <persistence-unit name="default" transaction-type="JTA">
        <properties>
            <property name="jakarta.persistence.schema-generation.database.action" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

This configuration uses the default data source in Eclipse GlassFish, which points to a Derby database. Make sure Derby is running using the start-database command.

Custom Data Source Configuration (Recommended)

For production applications, configure a custom data source using the Eclipse GlassFish administration console or CLI commands:

# Create connection pool for PostgreSQL
asadmin create-jdbc-connection-pool \
  --datasourceclassname org.postgresql.ds.PGSimpleDataSource \
  --restype javax.sql.DataSource \
  --property user=myuser:password=mypassword:databaseName=mydb:serverName=localhost:port=5432 \
  MyPool

# Create JDBC resource
asadmin create-jdbc-resource --connectionpoolid MyPool jdbc/MyDataSource

Adjust the datasourceclassname and properties according to your database and JDBC driver.

For information on how to install JDBC drivers in Eclipse GlassFish, see Administering Database Connectivity.
For more information about these commands, see create-jdbc-connection-pool and create-jdbc-resource in the Reference Manual.

Then reference the custom data source in your persistence.xml:

<persistence-unit name="myPU" transaction-type="JTA">
    <jta-data-source>jdbc/MyDataSource</jta-data-source>
    <properties>
        <property name="jakarta.persistence.schema-generation.database.action" value="create"/>
    </properties>
</persistence-unit>

EntityManager Integration

You can access the EntityManager used by the repository for custom operations:

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

    // Method to access the repository's EntityManager
    EntityManager getEntityManager();

    // Custom method using the repository's EntityManager
    default List<Product> findProductsWithComplexLogic(String searchTerm) {
        EntityManager em = getEntityManager();
        return em.createQuery(
            "SELECT p FROM Product p WHERE p.name LIKE :term OR p.description LIKE :term",
            Product.class)
            .setParameter("term", "%" + searchTerm + "%")
            .getResultList();
    }
}

EntityManager Configuration Options

By default, Jakarta Data repositories use the default EntityManager. You can customize this behavior:

Option 1: Specify persistence unit name

Use the dataStore attribute of the @Repository annotation to specify which persistence unit the repository should use.

@Repository(dataStore = "myPU")
public interface ProductRepository extends CrudRepository<Product, Long> {
    // Repository methods
}

Option 2: Use CDI qualifiers

Define a custom CDI qualifier and use it to inject a specific EntityManager.

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

    @CustomDB
    EntityManager getEntityManager();
}

Option 3: Programmatic EntityManager selection

Define a default method in the repository interface that returns an EntityManager. This approach allows you to programmatically select the appropriate EntityManager using CDI lookup, JNDI lookup, or any other mechanism.

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

    default EntityManager getEntityManager() {
        return CDI.current().select(EntityManager.class, new CustomDB.Literal()).get();
    }
}

Mixing Repository and EntityManager Operations

Repository methods and direct EntityManager operations can be used together in the same transaction when they use the same persistence unit:

@ApplicationScoped
@Transactional
public class CustomerService {

    @Inject
    private EntityManager entityManager;

    @Inject
    private CustomerRepository customerRepository;

    public Customer updateCustomerWithAudit(Long customerId, CustomerUpdate update) {

        EntityManager repositoryEntityManager = customerRepository.getEntityManager();

        // Use repository for simple operations
        Customer customer = customerRepository.findById(customerId)
            .orElseThrow(() -> new EntityNotFoundException("Customer not found"));

        // Use EntityManager for complex operations
        AuditLog audit = new AuditLog();
        audit.setAction("UPDATE_CUSTOMER");
        audit.setEntityId(customerId);
        audit.setTimestamp(LocalDateTime.now());
        entityManager.persist(audit);

        // Alternatively, use the repository's EntityManager to ensure the same persistence context
        repositoryEntityManager.flush();

        // Update using repository
        customer.setName(update.getName());
        customer.setEmail(update.getEmail());

        return customerRepository.save(customer);
    }
}

Configuring NoSQL Data Repositories

To use Jakarta Data repositories with NoSQL entities in your Eclipse GlassFish application, you need to:

  1. Add Jakarta Data and NoSQL API dependencies to your project

  2. Add NoSQL database driver dependency

  3. Configure database connection properties

  4. Define NoSQL entities using Jakarta NoSQL annotations

Adding Dependencies

Add the Jakarta Data API dependency (as provided - only compile-time, not included in the application):

<dependency>
    <groupId>jakarta.data</groupId>
    <artifactId>jakarta.data-api</artifactId>
    <version>1.0.1</version>
    <scope>provided</scope>
</dependency>

Add the Jakarta NoSQL API dependency (as provided - only compile-time, not included in the application):

<dependency>
    <groupId>jakarta.nosql</groupId>
    <artifactId>jakarta.nosql-api</artifactId>
    <version>1.0.1</version>
    <scope>provided</scope>
</dependency>

Add the appropriate Eclipse JNoSQL database driver (the driver and its dependencies should be added to the application). For example, for MongoDB:

<dependency>
    <groupId>org.eclipse.jnosql.databases</groupId>
    <artifactId>jnosql-mongodb</artifactId>
    <version>{jnosql-version}</version>
</dependency>
Eclipse GlassFish does not bundle NoSQL database drivers. You must add the appropriate Eclipse JNoSQL database dependency and its transitive dependencies to your application. If you use Maven or a similar build tool, it will pull all transitive dependencies automatically.

NoSQL Database Categories

Eclipse JNoSQL supports four main categories of NoSQL databases:

Category Examples Use Cases

Document

MongoDB, CouchDB, CouchBase, ArangoDB

Content management, catalogs, user profiles with nested data

Key-Value

Redis, Hazelcast, Memcached, Riak

Caching, session storage, shopping carts

Wide-Column

Cassandra, HBase, DynamoDB

Time-series data, IoT applications, analytics

Graph

Neo4j, ArangoDB, OrientDB

Social networks, recommendation engines, fraud detection

NoSQL Entity Definition

NoSQL entities use Jakarta NoSQL annotations:

Annotation Description

@jakarta.nosql.Entity

Specifies that the class is a NoSQL entity

@jakarta.nosql.Id

Specifies the primary key of the entity

@jakarta.nosql.Column

Maps a field to a database column/attribute

@jakarta.nosql.Convert

Specifies a converter for the field

@jakarta.nosql.Embeddable

Specifies that the class is embeddable

@jakarta.nosql.Inheritance

Specifies inheritance mapping strategy for entities

@jakarta.nosql.DiscriminatorColumn

Specifies the discriminator column for the inheritance mapping strategy

@jakarta.nosql.DiscriminatorValue

Specifies the discriminator value for the inheritance mapping strategy

@jakarta.nosql.MappedSuperclass

Specifies a class whose mapping information is applied to entities that inherit from it

Key differences from JPA annotations:

  • No @Table annotation - collection/table name is specified in @Entity

  • No relationship annotations (@OneToMany, @ManyToOne) - embed or reference manually

  • No @GeneratedValue - primary keys must be assigned manually or assigned automatically be the database

  • No @Version for optimistic locking

  • No @Transient - non-annotated fields are ignored by default

  • Java record types are supported for read-only operations

NoSQL Entity Examples

Class-based entity with embedded objects:

import jakarta.nosql.Entity;
import jakarta.nosql.Id;
import jakarta.nosql.Column;
import jakarta.nosql.Embeddable;

@Entity("customers")
public class Customer {

    @Id
    private String id;

    @Column
    private String name;

    @Column
    private String email;

    @Column
    private Address address; // Embedded object

    @Column
    private List<String> tags;

    @Column
    private Map<String, Object> metadata;

    // Constructors, getters, and setters
}

@Embeddable
public class Address {
    @Column
    private String street;

    @Column
    private String city;

    @Column
    private String country;

    // Constructors, getters, and setters
}

Record-based entity (read-only):

@Entity("sensor_data")
public record SensorReading(
    @Id String id,
    @Column String sensorId,
    @Column LocalDateTime timestamp,
    @Column Double temperature,
    @Column Double humidity,
    @Column Map<String, Double> additionalMetrics
) {}

Repository Usage with NoSQL Entities

NoSQL entities work with the same repository interfaces as JPA entities:

@Repository
public interface CustomerRepository extends CrudRepository<Customer, String> {

    @Find
    List<Customer> findCustomers(@By("name") String name);

    @Find
    List<Customer> findCustomers(@By("address.city") String city);

    @Query("WHERE tags IN :tags")
    List<Customer> findCustomersByTags(List<String> tags);

    @Count
    long countCustomers(@By("address.country") String country);
}

@Repository
public interface SensorReadingRepository extends CrudRepository<SensorReading, String> {

    @Find
    List<SensorReading> findReadings(@By("sensorId") String sensorId);

    @Query("WHERE timestamp BETWEEN :start AND :end")
    List<SensorReading> findByTimestampRange(LocalDateTime start, LocalDateTime end);

    @Count
    long countBySensorId(@By("sensorId") String sensorId);
}

Database Configuration

NoSQL database connections are configured using MicroProfile Config properties. Configuration varies by database type and specific database.

MongoDB Configuration Example:

Add properties to your microprofile-config.properties file:

# Document database configuration
jnosql.document.database=mystore
jnosql.mongodb.host=localhost:27017
jnosql.mongodb.user=myuser
jnosql.mongodb.password=mypassword

MongoDB-specific properties:

Property Description

jnosql.mongodb.host

MongoDB server hostname and port (default: localhost:27017)

jnosql.mongodb.user

Username for MongoDB authentication

jnosql.mongodb.password

Password for MongoDB authentication

jnosql.mongodb.authentication.source

The source where the user is defined

jnosql.mongodb.authentication.mechanism

Authentication mechanism (see MongoDB JavaDoc)

Common Configuration Properties:

Property Description

jnosql.<type>.provider

Fully qualified class name of the database configuration provider

jnosql.<type>.database

Database name or identifier

jnosql.<database>.host

Database host and port (format: host:port)

jnosql.<database>.user

Username for authentication

jnosql.<database>.password

Password for authentication

jnosql.<database>.timeout

Connection timeout in milliseconds

Where <type> is one of: document, keyvalue, column, graph and <database> is the specific database name.

Adding NoSQL Database Dependencies

To use a specific NoSQL database, add the corresponding Eclipse JNoSQL database dependency. For example:

MongoDB (Document Database):

<dependency>
    <groupId>org.eclipse.jnosql.databases</groupId>
    <artifactId>jnosql-mongodb</artifactId>
    <version>{jnosql-version}</version>
</dependency>

Redis (Key-Value Database):

<dependency>
    <groupId>org.eclipse.jnosql.databases</groupId>
    <artifactId>jnosql-redis</artifactId>
    <version>{jnosql-version}</version>
</dependency>

Cassandra (Wide-Column Database):

<dependency>
    <groupId>org.eclipse.jnosql.databases</groupId>
    <artifactId>jnosql-cassandra</artifactId>
    <version>{jnosql-version}</version>
</dependency>

Neo4j (Graph Database):

<dependency>
    <groupId>org.eclipse.jnosql.databases</groupId>
    <artifactId>jnosql-neo4j</artifactId>
    <version>{jnosql-version}</version>
</dependency>

Supported NoSQL Databases

For an up-to-date list of supported databases and driver installation instructions, see the Eclipse JNoSQL Databases repository.

Document Databases: MongoDB, CouchDB, CouchBase, OrientDB, ArangoDB, RavenDB, Elasticsearch, Solr

Key-Value Databases: Redis, Hazelcast, Infinispan, Memcached, Riak, Oracle NoSQL, ArangoDB

Wide-Column Databases: Cassandra, HBase, DynamoDB

Graph Databases: Neo4j, OrientDB, ArangoDB, TinkerPop-compatible databases

Programmatic Configuration

You can configure NoSQL databases programmatically by creating a configuration supplier. The supplier is typically a CDI bean that implements the Supplier interface. it should be annotated with @Alternative and @Priority to override the default configuration.

For example, a supplier for MongoDB:

@ApplicationScoped
@Alternative
@Priority(Interceptor.Priority.APPLICATION)
public class MongoDBManagerSupplier implements Supplier<DocumentManager> {

    @Produces
    public DocumentManager get() {
        Settings settings = Settings.builder()
            .put("jnosql.mongodb.host", "localhost:27017")
            .put("jnosql.document.database", "mystore")
            .build();

        MongoDBDocumentConfiguration configuration = new MongoDBDocumentConfiguration();
        DocumentManagerFactory factory = configuration.apply(settings);
        return factory.apply("mystore");
    }
}

Transaction Management

Jakarta Data repositories integrate with Jakarta EE transaction management for JPA entities. Repository methods automatically participate in existing transactions or create new ones as needed.

Transaction management is currently only supported for JPA entities. NoSQL repositories do not support JTA transactions yet.

Declarative Transactions

import jakarta.ejb.Stateless;
import jakarta.transaction.Transactional;

@ApplicationScoped
public class OrderService {

    @Inject
    private OrderRepository orderRepository;

    @Inject
    private ProductRepository productRepository;

    @Transactional
    public Order createOrder(OrderRequest request) {
        // All repository operations participate in the same transaction
        Product product = productRepository.findById(request.getProductId())
            .orElseThrow(() -> new IllegalArgumentException("Product not found"));

        Order order = new Order();
        order.setProduct(product);
        order.setQuantity(request.getQuantity());
        order.setCustomerId(request.getCustomerId());

        return orderRepository.save(order);
    }
}

Custom Transaction Behavior

You can override the default transaction behavior using the @Transactional annotation:

@Repository
public interface AuditRepository extends CrudRepository<AuditLog, Long> {

    @Transactional(Transactional.TxType.REQUIRES_NEW)
    AuditLog save(AuditLog auditLog);
}

By default, repository methods use REQUIRED transaction type, which means they join an existing transaction or create a new one if none exists.

Jakarta Validation Support

Jakarta Data repositories integrate with Jakarta Validation to validate method parameters and return values for JPA entities.

Validation is currently only supported for JPA entities. NoSQL repositories do not support Jakarta Validation yet.

Parameter Validation

For simple field validation, use annotations directly on parameters:

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

    @Find
    List<Product> findProducts(@By("price") @Max(1000) @Min(0) BigDecimal maxPrice);

    @Find
    List<Product> findProducts(@By("category") @NotBlank String category);
}

For entities with validation annotations, use @Valid on the parameter:

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

    @Insert
    Product insertProduct(@Valid Product product);

    @Update
    Product updateProduct(@Valid Product product);

    @Save
    Product saveProduct(@Valid Product product);
}

Handling Validation Errors

Validation errors are thrown as ConstraintViolationException:

@ApplicationScoped
public class ProductService {

    private static final System.Logger logger = System.getLogger(ProductService.class.getName());

    @Inject
    private ProductRepository productRepository;

    public Product createProduct(Product product) {
        try {
            return productRepository.insertProduct(product);
        } catch (ConstraintViolationException e) {
            Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
            for (ConstraintViolation<?> violation : violations) {
                String property = violation.getPropertyPath().toString();
                String message = violation.getMessage();
                logger.log(System.Logger.Level.ERROR,
                    () -> "Validation error on " + property + ": " + message);
            }
            throw e;
        }
    }
}

Limitations and Considerations

General Limitations

Single NoSQL Database Type per Application

Eclipse GlassFish supports only one NoSQL database per application instance.

On the other hand, you can use multiple JPA persistence units in the same application. You can also use both JPA and NoSQL Data repositories simultaneously in the same application.

Repository Interface Restrictions

  • Each repository must be dedicated to a single entity type

  • For JPA entities, each repository must use a single persistence unit

  • You cannot mix JPA and NoSQL entities in the same repository interface

Mixed JPA and NoSQL Entity Usage

You can use both JPA entities and NoSQL entities in the same Eclipse GlassFish application by defining separate repository interfaces for each entity type and configuring the appropriate database connections. However, there are important restrictions:

Separate Repository Interfaces Required You cannot mix JPA entities and NoSQL entities in the same repository interface. Each repository must be dedicated to a single entity type:

// JPA repository - works with relational database
@Repository
public interface JpaProductRepository extends CrudRepository<JpaProduct, Long> {
    @Find
    List<JpaProduct> findProducts(@By("category") String category);
}

// NoSQL repository - works with NoSQL database
@Repository
public interface NoSqlProductRepository extends CrudRepository<NoSqlProduct, String> {
    @Find
    List<NoSqlProduct> findProducts(@By("category") String category);
}

Separate Database Configurations Configure separate database connections for JPA and NoSQL entities:

  • JPA entities use persistence.xml and JDBC data sources

  • NoSQL entities use MicroProfile Config properties

No Cross-Database Transactions Transactions cannot span across JPA and NoSQL databases. Each database type manages its own transaction context.

JPA Entity Considerations

  • Full JTA transaction support

  • Jakarta Validation integration

  • JPQL and JDQL query support

NoSQL Entity Limitations

Current Limitations:

No Jakarta Validation Support Validation annotations on NoSQL entities and repository parameters are ignored. Implement validation manually:

@ApplicationScoped
public class NoSQLProductService {

    private static final System.Logger logger = System.getLogger(NoSQLProductService.class.getName());

    @Inject
    private NoSQLProductRepository repository;

    public NoSQLProduct createProduct(NoSQLProduct product) {
        // Manual validation
        if (product.getName() == null || product.getName().isBlank()) {
            throw new IllegalArgumentException("Product name is required");
        }
        if (product.getPrice() == null || product.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Product price must be greater than zero");
        }

        logger.log(System.Logger.Level.INFO, () -> "Creating product: " + product.getName());
        return repository.save(product);
    }
}

No JTA Transaction Support NoSQL repositories do not participate in JTA transactions. Manage transactions manually if supported by your database:

@ApplicationScoped
public class NoSQLOrderService {

    private static final System.Logger logger = System.getLogger(NoSQLOrderService.class.getName());

    @Inject
    private NoSQLOrderRepository orderRepository;

    @Inject
    private NoSQLCustomerRepository customerRepository;

    public void processOrder(NoSQLOrder order) {
        // No automatic transaction management
        // Implement compensation logic if needed
        try {
            orderRepository.save(order);
            // Update customer separately - no transaction guarantees
            NoSQLCustomer customer = customerRepository.findById(order.getCustomerId())
                .orElseThrow();
            customer.incrementOrderCount();
            customerRepository.save(customer);

            logger.log(System.Logger.Level.INFO, () -> "Order processed successfully");
        } catch (Exception e) {
            logger.log(System.Logger.Level.ERROR, () -> "Failed to process order: " + e.getMessage());
            // Implement manual rollback if needed
            throw e;
        }
    }
}

Single NoSQL Database per Application You can only configure one NoSQL database per application instance.

Common Issues and Troubleshooting

Configuration Issues:

  • Verify database drivers are properly installed

  • Check MicroProfile Config properties syntax

  • Ensure persistence.xml is in the correct location

  • Validate JNDI resource names match configuration

Runtime Issues:

  • Check database connectivity and credentials

  • Verify entity annotations are correct

  • Ensure repository interfaces are properly annotated

  • Review application logs for detailed error messages

For more detailed troubleshooting, enable debug logging:

# Enable Jakarta Data and JNoSQL debug logging
org.eclipse.jnosql.level=FINE
org.glassfish.main.jnosql.level=FINE
# Enable EclipseLink (Jakarta Persistence) logging
org.eclipse.persistence.level=FINE