Common recipes for mansart-jakarta-data, mansart-transactions, and mansart-pool. The exhaustive reference (all annotations, all configuration options) lives in Reference.

Jakarta Data repositories

BasicRepository<T, K> and CrudRepository<T, K>

BasicRepository covers save, saveAll, findById, findAll, deleteById, delete, deleteAll. CrudRepository adds insert/insertAll/update/updateAll. To go further, declare specialized methods on the interface; the APT turns them into static query plans.

@Repository
public interface AuthorRepository extends CrudRepository<Author, Long> {
    long count();
    boolean existsById(Long id);
}

Derived queries (by method name)

Mansart parses the name into a neutral AST, then produces SQL via the target dialect. Supported keywords: findBy, existsBy, countBy, deleteBy, operators And, Or, Like, Between, LessThan, GreaterThan, LessThanEqual, GreaterThanEqual, IgnoreCase, OrderBy<Asc|Desc>.

List<Author> findByName(String name);
Optional<Author> findOneByName(String name);
List<Author> findByNameLike(String pattern);
List<Author> findByNameIgnoreCase(String name);
List<Author> findAllByOrderByNameAsc();
long deleteByName(String name);

@Find (typed parameter matching)

@Find
List<Book> find(String title, Author author);

The Java parameter name must match the entity attribute (compile with -parameters); each parameter is an equality match. @Find methods are currently served by the reflective runtime fallback — range conditions (Between, LessThan, …) belong to derived method names such as findByPublishedOnBetween(from, to), which the APT compiles.

@Query JDQL

JDQL is the query dialect defined by Jakarta Data 1.0. Simpler than JPQL, sufficient for typed SELECT/UPDATE/DELETE.

@Query("FROM Author WHERE name LIKE :pattern AND id > :minId")
List<Author> search(String pattern, Long minId);

@Query("UPDATE Author SET name = :newName WHERE name = :oldName")
long rename(String oldName, String newName);

Pagination — offset and keyset

Page<Author> findByNameLikeOrderByNameAsc(String pattern, PageRequest page);

// Call
var page = authors.findByNameLikeOrderByNameAsc("S%", PageRequest.ofPage(1).size(20));

Keyset cursors (PageRequest.afterCursor(cursor)) avoid drift on long pagination.

Lifecycle annotations

@Insert, @Update, @Delete, @Save typed: parameter = entity, collection or varargs; return void/T/Iterable<T>/int/long/boolean. See Reference.

Transactions

@Transactional (CDI)

The mansart-transactions interceptor materializes @jakarta.transaction.Transactional (TxType REQUIRED, REQUIRES_NEW, MANDATORY, SUPPORTS, NEVER, NOT_SUPPORTED).

@ApplicationScoped
public class AuthorService {

    @Inject AuthorRepository authors;

    @Transactional
    public Author register(String name) {
        return authors.save(new Author(name));
    }

    @Transactional(Transactional.TxType.REQUIRES_NEW)
    public void audit(String message) { /* ... */ }
}

UserTransaction (programmatic)

@Inject UserTransaction tx;

void run() throws Exception {
    tx.begin();
    try {
        authors.save(new Author("Marguerite Yourcenar"));
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        throw e;
    }
}

Suspend / resume

TransactionManager.suspend() and resume(Transaction) cover batch / scheduler cases. The transactional context is carried in a ThreadLocal — a deliberate choice: the Jakarta Transactions API is imperative (begin() returns, commit() comes later from arbitrary call sites), which cannot fit ScopedValue’s enclosing-scope model; `ThreadLocal works on virtual threads exactly as on platform threads.

Connection pool

Minimal configuration

var ds = MansartDataSource.of(PoolConfig.builder()
    .jdbcUrl("jdbc:postgresql://db.local:5432/shop")
    .username("shop").password("...")
    .minIdle(4).maxSize(32)
    .acquireTimeout(Duration.ofSeconds(5))
    .idleTimeout(Duration.ofMinutes(10))
    .maxLifetime(Duration.ofMinutes(30))
    .validation(ValidationMode.ON_BORROW)
    .build());

Sizing

With virtual threads, the classic "threads = connections" sizing disappears: dimension on real DB concurrency, not application concurrency. Rule of thumb: maxSize ≈ DB vCPUs × 2..4. Beyond that, the DB becomes the bottleneck — see mansart-pool BENCH.md.

Leak detection

leakDetectionThreshold(Duration) logs a warning + acquisition stack trace when a connection stays borrowed longer than the threshold. Enable in staging, disable in production if the overhead is measured.

Named datasources (dataStore) in Vidocq NEW

In a Vidocq application, the pool is configured declaratively (the vidocq-runtime-mansart-pool-extension reads vidocq.pool. keys) and one application can talk to several databases. Names are *structural (fixed at build time), values are runtime MicroProfile Config.

Declare a named pool alongside the default one — the name is the single path segment after vidocq.pool.:

# @Default datasource
vidocq.pool.url=jdbc:postgresql://db.local:5432/shop
vidocq.pool.username=shop
vidocq.pool.maxSize=16

# named datasource "audit"
vidocq.pool.audit.url=jdbc:postgresql://db.local:5432/audit
vidocq.pool.audit.username=audit
vidocq.pool.audit.maxSize=4

Every key of PoolConfig has its property twin (minIdle, maxSize, acquireTimeout, idleTimeout, maxLifetime, validationTimeout, validation, validationQuery, leakDetectionThreshold), plus password, xa (XA enlistment) and xaDataSourceClass, for the default and for each name. Names can also be declared with @VidocqDataSources({"audit"}) on the application class; the two declaration paths are merged and deduplicated.

Route a repository to a named datasource with the standard Jakarta Data attribute:

@Repository(dataStore = "audit")
public interface AuditEntryRepository extends BasicRepository<AuditEntry, Long> { }

And inject the raw datasource anywhere with the generated @Named holder:

@Inject
@Named("audit")
DataSource auditDataSource;

Named datasources need the vidocq-runtime-mansart-pool-datasources-codegen jar on the compiler’s annotationProcessorPaths (it generates the @Named holder beans). The end-to-end walkthrough — including this wiring — is the REST + database tutorial; the runnable reference is vidocq-runtime-examples/vidocq-runtime-mansart-h2-example in the runtime repo.

Repositories routed via dataStore are built by the repository factory without interceptor enhancement; transactional semantics are handled, but custom CDI interceptors on such repositories are not applied today (tracked in the repo’s BUG.md).

In dev mode, the runtime’s PostgreSQL Dev Service can provision one disposable container per declared datasource name — nothing to install or configure. See DEV_SERVICES.md in the Vidocq runtime repo.

Composition with other modules

  • Vauban CDI — produces @Repository beans, runs the @Transactional interceptor.

  • Cassini REST — REST endpoints calling transactional services.

  • Vidocq Runtime — orchestrator that assembles pool + tx + data + REST into an AOT-friendly fat jar.