This page shows how to lay the first stone: an entity, a Jakarta Data repository, a Mansart DataSource, and a transaction. Targets H2 in-memory for speed; switching to PostgreSQL only changes the configuration.

Prerequisites

  • Java 25 (Temurin) + Maven 3.9.16.

  • JDBC driver provided by the application (com.h2database:h2 or org.postgresql:postgresql).

  • cd mansart && sdk env (or equivalent .sdkmanrc) to pin the JVM and Maven.

Maven dependencies

<dependencies>
  <dependency>
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-data-core</artifactId>
    <version>${mansart.version}</version>
  </dependency>
  <dependency>
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-data-dialect-h2</artifactId>
    <version>${mansart.version}</version>
  </dependency>
  <dependency>
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-pool-core</artifactId>
    <version>${mansart.version}</version>
  </dependency>
  <dependency>
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-transactions-cdi</artifactId>
    <version>${mansart.version}</version>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <annotationProcessorPaths>
          <path>
            <groupId>io.vidocq.mansart</groupId>
            <artifactId>mansart-data-processor</artifactId>
            <version>${mansart.version}</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>

The mansart-data-processor APT generates, at compile time, the metamodel (_Author) and the AuthorRepositoryImpl implementation. No runtime generation.

An entity

package shop;

import jakarta.persistence.*;

@Entity
@Table(name = "authors")
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    // getters / setters / no-arg constructor
}

A repository

package shop;

import jakarta.data.repository.BasicRepository;
import jakarta.data.repository.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface AuthorRepository extends BasicRepository<Author, Long> {

    long count();
    boolean existsById(Long id);

    List<Author> findByName(String name);
    Optional<Author> findOneByName(String name);
    long deleteByName(String name);
}

The APT generates shop.AuthorRepositoryImpl next to it, plus an entry in META-INF/mansart-repositories.list.

Standalone bootstrap

import io.vidocq.mansart.pool.PoolConfig;
import io.vidocq.mansart.pool.core.MansartDataSource;
import io.vidocq.mansart.data.core.MansartData;

var dataSource = MansartDataSource.of(PoolConfig.builder()
    .jdbcUrl("jdbc:h2:mem:shop;DB_CLOSE_DELAY=-1")
    .username("sa").password("")
    .minIdle(2).maxSize(10)
    .build());

var mansart = MansartData.builder().dataSource(dataSource).build();
var authors = mansart.repository(AuthorRepository.class);

authors.save(new Author("Sylvie Germain"));
authors.findByName("Sylvie Germain").forEach(System.out::println);

CDI bootstrap (Vauban)

@Inject AuthorRepository authors;

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

The mansart-data-cdi BCE reads META-INF/mansart-repositories.list and declares an @ApplicationScoped bean for each @Repository interface detected. The mansart-transactions interceptor materializes @Transactional. See Vauban for the CDI container details.

Wiring the BCE into the Vauban build

Add mansart-data-cdi to your dependencies — and, crucially, to the annotation-processor path. Vauban resolves injections at compile time inside its annotation processor, which discovers CDI Build Compatible Extensions via ServiceLoader on the processor path: the moment your pom declares <annotationProcessorPaths> (it does, for mansart-data-processor), javac sees only those entries, and a BCE left on the plain compile path silently never runs. The tell-tale failure is a build-time [Vauban] Unsatisfied dependency: …​ of type ClassType[name=io.vidocq.mansart.data.core.RepositoryRuntime].

<annotationProcessorPaths>
  <path>
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-data-processor</artifactId>
    <version>${mansart.version}</version>
  </path>
  <path><!-- vauban-processor, if not already on the processor path -->
    <groupId>io.vidocq.vauban</groupId>
    <artifactId>vauban-processor</artifactId>
    <version>${vauban.version}</version>
  </path>
  <path><!-- the BCE contributing MansartRuntimeProducer must be visible to the Vauban APT -->
    <groupId>io.vidocq.mansart</groupId>
    <artifactId>mansart-data-cdi</artifactId>
    <version>${mansart.version}</version>
  </path>
</annotationProcessorPaths>

Two related notes:

  • if some other cross-module bean still trips the build-time validator, pass -Avauban.validation=false to the compiler to defer validation to runtime (that is what Mansart’s own module-path integration tests do);

  • at runtime, the default RepositoryRuntime needs a @Default javax.sql.DataSource bean (a @Produces DataSource backed by your driver or mansart-pool) — the producer fails fast with an explicit message otherwise. No requires io.vidocq.mansart.data.cdi is needed in your module-info.java: boot-layer service binding pulls the module in through its provides BuildCompatibleExtension.

Full example in Vidocq Runtime

The vidocq-runtime-mansart-h2-example example (in the Vidocq Runtime repo) shows end-to-end wiring: MansartDataSource + mansart-jakarta-data + @Transactional + Cassini REST endpoint. It is the reference sandbox to validate an integration.

Next step

  • Usage: pagination, JDQL, nested transactions.

  • Concepts: Repository, EntityManager, Transaction, Pool.