Meet Jakarta Data: The Newest Member of the Jakarta EE 11 Ecosystem¶
Jakarta Data is a new specification in Jakarta EE 11 that simplifies data access across different storage technologies. It is storage-agnostic — not limited to Jakarta Persistence or relational databases — and is designed so providers can adapt it to a variety of backends. The Eclipse JNoSQL project also implements this specification, bringing the same API surface to the NoSQL ecosystem.
Exploring Jakarta Data¶
Similar to Spring Data Commons, Micronaut Data, and Quarkus Panache, Jakarta Data introduces a Repository abstraction. This includes a basic DataRepository interface to indicate a repository, as well as two interfaces, BasicRepository and CrudRepository, which provide common CRUD operations for underlying data storage. It also introduces a new annotation, @Repository, to mark an interface as a repository, whether it is derived from the common interfaces or is a pure interface that does not extend any existing interface.
For example, the following PostRepository interface is for the entity Post.
In addition, Jakarta Data supports derived queries by method name conventions, pagination, and custom queries using @Query annotations. If you have experience with Spring Data or Micronaut, you will be familiar with these features.
@Repository
public interface PostRepository extends CrudRepository<Post, UUID> {
Optional<Post> findByTitle(String title);
Page<Post> findByTitleLike(String titleLike, PageRequest pageRequest);
@Query("where title like :title")
@OrderBy("title")
Post[] byTitleLike(@Param("title") String title);
}
Additionally, Jakarta Data provides a collection of lifecycle annotations (Find, Insert, Update, Delete) that allow you to write operation methods more freely in your own interfaces. The entity type can be determined from the method parameters or the return type.
@Repository
public interface Blogger {
@Query("""
SELECT p.id, p.title FROM Post AS p
WHERE p.title LIKE :title
ORDER BY p.createdAt DESC
""")
Page<PostSummary> allPosts(@Param("title") String title, PageRequest page);
@Find
@OrderBy("createdAt")
List<Post> byStatus(Status status, Order<Post> order, Limit limit);
@Find
Optional<Post> byId(UUID id);
@Insert
Post insert(Post post);
@Update
Post update(Post post);
@Delete
void delete(Post post);
}
Currently, Quarkus and Micronaut have already integrated Jakarta Data as an alternative persistence solution for developers. I have written articles introducing the integration of Jakarta Data with Quarkus and Micronaut. Spring and Spring Data have no plans to integrate Jakarta Data, but that does not mean integrating Jakarta Data with Spring is difficult. I also wrote a post about integrating Hibernate Data Repositories with Spring.
Unlike Jakarta Persistence, Spring Data, and Micronaut Data, Jakarta Data 1.0 does not provide specific annotations to define entity types. As a result, it relies heavily on each provider's implementation details. For example, Micronaut Data reuses Jakarta Persistence annotations as well as its own data annotations, both of which work seamlessly with Jakarta Data. Quarkus and WildFly integrate Jakarta Data via Hibernate Data repositories, so in these environments, Jakarta Persistence entities are used to represent Jakarta Data entities.
Currently, open-source Jakarta EE implementors such as GlassFish, WildFly, and Open Liberty are working on their own Jakarta Data implementations, typically leveraging entities defined with Jakarta Persistence. However, their approaches vary. WildFly (with Hibernate) translates Jakarta Data queries into Java code and generates repository implementations at compile time. In contrast, GlassFish reuses the effort from Eclipse JNoSQL and dynamically processes queries at runtime.
In this post, we’ll focus on demonstrating Jakarta Data features on standard Jakarta EE-compatible application servers, such as GlassFish, WildFly, and others.
You can get the example project from my GitHub and explore it yourself.
WildFly¶
WildFly has provided Jakarta Data as a preview feature since version 34. In the latest WildFly 37 preview, Jakarta Data support has been updated to align with Hibernate 7 and Jakarta Persistence 3.2.
To use Jakarta Data in WildFly, configure the hibernate-processor in your Maven compiler plugin. This processes your Repository interfaces and generates implementation classes at compile time.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-processor</artifactId>
<version>${hibernate.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
Open a terminal window, navigate to the project root folder, run the following command to compile the project. This will generate repository source code using the configured Hibernate Processor:
The generated repository implementation classes use Hibernate’s StatelessSession to implement all methods. For example, the generated target/generated-sources/annotations/com/example/repository/PostRepository_.java file looks like this:
/**
* Implements Jakarta Data repository {@link com.example.repository.PostRepository}
**/
@Dependent
@Generated("org.hibernate.processor.HibernateProcessor")
public class PostRepository_ implements PostRepository {
protected @Nonnull StatelessSession session;
public PostRepository_(@Nonnull StatelessSession session) {
this.session = session;
}
public @Nonnull StatelessSession session() {
return session;
}
@PersistenceUnit
private EntityManagerFactory sessionFactory;
@PostConstruct
private void openSession() {
session = sessionFactory.unwrap(SessionFactory.class).openStatelessSession();
}
@PreDestroy
private void closeSession() {
session.close();
}
@Inject
PostRepository_() {
}
//... other methods
/**
* Find {@link Post}.
*
* @see com.example.repository.PostRepository#findAll(PageRequest,Order)
**/
@Override
public Page<Post> findAll(PageRequest pageRequest, Order<Post> sortBy) {
var _builder = session.getCriteriaBuilder();
var _query = _builder.createQuery(Post.class);
var _entity = _query.from(Post.class);
_query.where(
);
var _spec = SelectionSpecification.create(_query);
for (var _sort : sortBy.sorts()) {
_spec.sort(asc(Post.class, _sort.property())
.reversedIf(_sort.isDescending())
.ignoringCaseIf(_sort.ignoreCase()));
}
try {
long _totalResults =
pageRequest.requestTotal()
? _spec.createQuery(session)
.getResultCount()
: -1;
var _results = _spec.createQuery(session)
.setFirstResult((int) (pageRequest.page()-1) * pageRequest.size())
.setMaxResults(pageRequest.size())
.getResultList();
return new PageRecord<>(pageRequest, _results, _totalResults);
}
catch (PersistenceException _ex) {
throw new DataException(_ex.getMessage(), _ex);
}
}
}
To run the project on a managed WildFly server, execute the following command:
You can find Jakarta Data usage examples in the testing codes.
The tests are written with Arquillian and JUnit 5, to run the tests on the managed WildFly with the Arquillian WildFly adapter:
GlassFish¶
Since GlassFish 8.0.0-M14, initial Jakarta Data support has been included. But unfortunately, I still encountered some issues running these examples on GlassFish.
Firstly, we used a query result projection to a Record class for Blogger.allPosts, which is a preview feature of the future Jakarta Data 1.1. It is not available in GlassFish. We have to change it to use an entity class in the return type or wrapped type.
@Repository
public interface Blogger {
@Query("""
SELECT p FROM Post AS p
WHERE p.title LIKE :title
ORDER BY p.createdAt DESC
""")
Page<Post> allPosts(@Param("title") String title, PageRequest page);
}
Unlike WildFly, which generates implementation code at compile time via the Hibernate processor, the Jakarta Data implementation on GlassFish is provided by the JNoSQL extension JNoSQL Jakarta Persistence, which handles Jakarta Data facilities at runtime.
To run the project on a managed GlassFish server, execute the following command:
To run the integration tests on a managed GlassFish server with the Arquillian GlassFish adapter, execute the following command:
[!NOTE] Currently, several tests are still failing because some fixes from the upstream JNoSQL Jakarta Persistence project have not been applied to the GlassFish repository. I have created some GitHub issues for the GlassFish project to track future updates.