Using Jakarta Data Repositories
- Using Jakarta Data Repositories
- Overview of Jakarta Data
- Choosing Between JPA and NoSQL
- Basic Repository Example
- Defining Repository Interfaces
- Query Methods
- Custom Queries
- Method Name-Based Queries (Deprecated)
- Pagination and Sorting
- Configuring JPA Data Repositories
- Configuring NoSQL Data Repositories
- Transaction Management
- Jakarta Validation Support
- Limitations and Considerations
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
}
Repository Interface (Recommended Approach)
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 withAscorDesc)
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:
-
Add Jakarta Data API dependency to your project
-
Configure a Jakarta Persistence persistence unit
-
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:
-
Add Jakarta Data and NoSQL API dependencies to your project
-
Add NoSQL database driver dependency
-
Configure database connection properties
-
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 |
|---|---|
|
Specifies that the class is a NoSQL entity |
|
Specifies the primary key of the entity |
|
Maps a field to a database column/attribute |
|
Specifies a converter for the field |
|
Specifies that the class is embeddable |
|
Specifies inheritance mapping strategy for entities |
|
Specifies the discriminator column for the inheritance mapping strategy |
|
Specifies the discriminator value for the inheritance mapping strategy |
|
Specifies a class whose mapping information is applied to entities that inherit from it |
Key differences from JPA annotations:
-
No
@Tableannotation - 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
@Versionfor optimistic locking -
No
@Transient- non-annotated fields are ignored by default -
Java
recordtypes 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 |
|---|---|
|
MongoDB server hostname and port (default: localhost:27017) |
|
Username for MongoDB authentication |
|
Password for MongoDB authentication |
|
The source where the user is defined |
|
Authentication mechanism (see MongoDB JavaDoc) |
Common Configuration Properties:
| Property | Description |
|---|---|
|
Fully qualified class name of the database configuration provider |
|
Database name or identifier |
|
Database host and port (format: host:port) |
|
Username for authentication |
|
Password for authentication |
|
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.xmland 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