Defining Jakarta Data Repositories with Hibernate: A Practical Guide

This article explains how to define Jakarta Data repositories using Hibernate, covering the stateless nature of repositories, minimal and extended interfaces, custom lifecycle annotations, finder methods, and custom JDQL queries, with concrete code examples and generated SQL illustrations.

JakartaEE China Community
JakartaEE China Community
JakartaEE China Community
Defining Jakarta Data Repositories with Hibernate: A Practical Guide

Repositories are a common pattern in Java persistence. Jakarta Data lets you define a repository as an interface, and the implementation is generated based on Jakarta Persistence or Jakarta NoSQL.

Two basic constraints apply: the repository and its entities are stateless—there is no caching, so you must call a repository method to persist any change. Retrieving the same entity multiple times yields distinct objects in memory.

Using Hibernate as the Jakarta Data implementation

Since Hibernate 6.6, the metamodel generator creates repository implementations at compile time. The generated code uses Hibernate’s StatelessSession, which does not manage entity lifecycle, lazy loading, or caching, making it well‑suited for Jakarta Data repositories. Implementations are placed in target/generated-sources in the same package as the repository interface, with a trailing underscore added to the class name.

package com.thorben.janssen.repository;
public class ChessPlayerRepository_ implements ChessPlayerRepository { … }

Minimal repository

The simplest repository is an empty interface annotated with @Repository:

@Repository
public interface ChessPlayerRepository { }

Although it provides no methods, you can extend a standard repository or add custom methods.

Extending standard repositories

Jakarta Data defines BasicRepository<Entity, Id> and CrudRepository<Entity, Id>. They declare methods such as:

<S extends T> S save(S entity)
<S extends T> List<S> saveAll(List<S> entities)
Optional<T> findById(@By(ID) K id)
Stream<T> findAll()
Page<T> findAll(PageRequest pageRequest, Order<T> sortBy)
void deleteById(@By(ID) K id)
void delete(T entity)
void deleteAll(List<? extends T> entities)
CrudRepository

extends BasicRepository and adds:

<S extends T> S insert(S entity)
<S extends T> List<S> insertAll(List<S> entities)
<S extends T> S update(S entity)
<S extends T> List<S> updateAll(List<S> entities)

Choose the interface that matches the methods you need, or define a completely custom repository.

Custom lifecycle methods

By defining methods and annotating them with @Insert, @Update, @Delete, or @Save, Jakarta Data generates the appropriate implementation that delegates to StatelessSession methods.

@Repository
public interface ChessTournamentRepository {
    @Insert
    void insert(ChessTournament tournament);
    @Insert
    void insertAll(List<ChessTournament> tournaments);
    @Update
    void update(ChessTournament tournament);
    @Delete
    void delete(ChessTournament tournament);
}

Queries

Two approaches are supported: finder methods using @Find and custom queries using @Query with Jakarta Data Query Language (JDQL).

Finder methods

A finder method is declared with @Find; Jakarta Data derives the query from the method parameters and return type. Example:

@Repository
public interface ChessTournamentRepository {
    @Find
    ChessTournament findTournament(String name, Integer edition);
}

Hibernate generates the following SQL, creating equal predicates for each parameter:

select ct1_0.id, ct1_0.edition, ct1_0.name, ct1_0.version
from ChessTournament ct1_0
where ct1_0.name=? and ct1_0.edition=?

If a method parameter name differs from the entity attribute, use @Param to map it:

@Find
Optional<ChessTournament> findByNameAndYear(String name,
    @Param("edition") Integer year);

The generated SQL includes predicates for both name and edition.

Custom queries

With @Query you can supply a JDQL fragment. JDQL uses entity and attribute names, not table or column names, and works with any Jakarta persistence implementation.

@Repository
public interface ChessTournamentRepository {
    @Query("WHERE name like :name")
    List<ChessTournament> findTournaments(String name);
}

Hibernate automatically adds the missing SELECT and FROM clauses and binds the :name parameter:

select ct1_0.id, ct1_0.edition, ct1_0.name, ct1_0.version
from ChessTournament ct1_0
where ct1_0.name like ? escape ''

Conclusion

Jakarta Data standardizes repository definitions for both Jakarta Persistence and Jakarta NoSQL. You can extend the provided BasicRepository or CrudRepository, or create a fully custom repository. In all cases the implementation is generated automatically, so tailoring a repository to your application’s needs requires minimal effort.

JavaPersistenceRepositoryHibernateJakarta DataStatelessSession
JakartaEE China Community
Written by

JakartaEE China Community

JakartaEE China Community, official website: jakarta.ee/zh/community/china; gitee.com/jakarta-ee-china; space.bilibili.com/518946941; reply "Join group" to get QR code

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.