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
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 :
-
Pour chaque classe annotée
@Entity(Mansart ou JPA) → génèreBook.java(métamodèle riche) etBook.java(métamodèle JPA standard). -
Pour chaque interface annotée
@Repository→ génèreBookRepositoryImpl.java(implémentation finale, classe publique) + une entrée dansMETA-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.
Pourquoi APT et pas du runtime codegen ?
-
AOT-friendly — GraalVM native-image, Leyden CDS : aucun proxy à enregistrer, tout est dans le bytecode au build.
-
Diagnosable — le code généré est lisible, débogable, montrable en stack trace.
-
Zéro warmup — pas de classloading dynamique au premier appel.
-
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 :
-
Connectiona 50+ méthodes — un proxy reflectif coûte uninvokepar 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
-
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
synchronizedqui englobe une opération bloquante (anti-pattern « thread pinning »). -
La connexion transactionnelle est portée par
ScopedValue<Connection> CURRENT— propagation automatique dansStructuredTaskScope.fork(…)sans code applicatif. -
mansart-poolvalide en CI avec-Djdk.tracePinnedThreads=fullqu’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
@Transactionalouvre/ferme la transaction. La connexion JDBC est enlistée commeXAResourcedégénéré (onePhase=true). -
Multi-resource (M4) — vrai 2PC :
preparepuiscommitsur chaqueXAResourceenlisté. Recovery log (M5) consigne les transactions enPREPAREDnon terminées pour récupération au reboot. -
Suspend / resume —
TransactionManager.suspend()retire le contexte duScopedValuecourant 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.