Cette page décrit ce qui se passe quand l’application appelle authors.findByName("Sylvie Germain") : du proxy CDI au pool, en passant par la traduction JDQL → SQL et la matérialisation ResultSet → entité. C’est aussi le bon endroit pour comprendre pourquoi Mansart privilégie le codegen statique.

Vue d’ensemble — appel d’une méthode repository

Diagram

Tout le chemin chaud est sans réflexion : les QueryPlan sont des constantes statiques préparées à <clinit>, le binding paramètre/attribut passe par des MethodHandle cachés une seule fois.

Codegen — APT (compile time)

mansart-data-processor est un javax.annotation.processing.Processor qui scrute les CU compilées :

  1. Pour chaque classe annotée @Entity (Mansart ou JPA) → génère Book.java (métamodèle riche) et Book.java (métamodèle JPA standard).

  2. Pour chaque interface annotée @Repository → génère BookRepositoryImpl.java (implémentation finale, classe publique) + une entrée dans META-INF/mansart-repositories.list.

Code source généré sous target/generated-sources/annotations/, marqué @Generated("io.vidocq.mansart.data.processor"). Le processeur n’a aucune dépendance runtime — il vit uniquement dans le classpath APT du module qui consomme.

Diagram

Pourquoi APT et pas du runtime codegen ?

  1. AOT-friendly — GraalVM native-image, Leyden CDS : aucun proxy à enregistrer, tout est dans le bytecode au build.

  2. Diagnosable — le code généré est lisible, débogable, montrable en stack trace.

  3. Zéro warmup — pas de classloading dynamique au premier appel.

  4. Compatible JPMS strict — les classes générées sont dans le même package que les sources, pas besoin d'`opens`.

Codegen — Class-File API (build time)

mansart-pool génère PooledConnectionProxy au build via JEP 484 Class-File API. Le proxy implémente java.sql.Connection, délègue toutes les méthodes à la connexion physique, et intercepte close() pour rendre la connexion au pool au lieu de la fermer.

Pourquoi Class-File API et pas java.lang.reflect.Proxy :

  • Connection a 50+ méthodes — un proxy reflectif coûte un invoke par appel.

  • La Class-File API produit du bytecode standard, JIT-friendly dès le premier appel.

  • Pas de dépendance ASM / Byte Buddy — règle « zéro dépendance externe » de l’écosystème.

Pool d’acquisition / restitution

Diagram
  • Semaphore permits — borne dure du pool, fair=false (perf).

  • ConcurrentLinkedDeque idle — pile lock-free, push/pop en tête (LIFO chaud).

  • ConcurrentHashMap.newKeySet inUse — set de connexions empruntées, pour la métrique et la détection de fuites.

  • Housekeeper virtual thread — réveil périodique, evict des connexions trop vieilles ou idle, pré-warm jusqu’à minSize.

Détails et chiffres : voir lien:https://codeberg.org/Vidocq/mansart/src/branch/main/mansart-pool/BENCH.md[mansart-pool BENCH.md].

Threading — virtual threads

  • Toutes les exécutions JDBC bloquantes (PreparedStatement.execute*, ResultSet.next) tournent sous virtual threads. Pas de pool plateforme.

  • La connexion JDBC n’est jamais détenue à travers un synchronized qui englobe une opération bloquante (anti-pattern « thread pinning »).

  • La connexion transactionnelle est portée par ScopedValue<Connection> CURRENT — propagation automatique dans StructuredTaskScope.fork(…​) sans code applicatif.

  • mansart-pool valide en CI avec -Djdk.tracePinnedThreads=full qu’aucun pinning n’apparaît sous 1000 borrows parallèles.

Mapping ResultSet → entité

Pas de réflexion. Le métamodèle _Book capture, dans <clinit>, un MethodHandle setter par attribut, obtenu via MethodHandles.privateLookupIn(Book.class, lookup). Pour chaque ligne :

Book row = constructor.invoke();           // MethodHandle constructeur
_Book.title.setter.invokeExact(row, rs.getString(1));
_Book.author.setter.invokeExact(row, refLoader.get(rs.getLong(2)));
// ...

invokeExact sur un MethodHandle est inliné par C2 — perf équivalente à du field access direct après warmup.

Transactions — propagation et XA

  • Single-resource (cas dominant) — l’intercepteur @Transactional ouvre/ferme la transaction. La connexion JDBC est enlistée comme XAResource dégénéré (onePhase=true).

  • Multi-resource (M4) — vrai 2PC : prepare puis commit sur chaque XAResource enlisté. Recovery log (M5) consigne les transactions en PREPARED non terminées pour récupération au reboot.

  • Suspend / resumeTransactionManager.suspend() retire le contexte du ScopedValue courant et le retourne ; resume(t) le réinstalle. Cas batch typique : worker job qui suspend la TX du caller pour faire un INSERT/COMMIT propre.

Voir lien:https://codeberg.org/Vidocq/mansart/src/branch/main/mansart-transactions/PLAN.md[mansart-transactions PLAN.md] pour la roadmap M1-M8.