Tentackle Persistence Wurblets — Implementation-Level Code Generation¶
Overview and Motivation¶
tentackle-persistence-wurblets is the build-time code generation module that produces the implementation
side of a Tentackle application's persistence: the executable SQL, the PreparedStatement plumbing, the caches,
the class variables, and the remote-delegate glue that make PDOs
actually load, store, update, and delete rows. It is the counterpart to
tentackle-wurblets, which generates the interface
side (constants, accessor declarations, DTOs, and model documentation). Both read the same application
model and the same
DataType/Backend definitions, so the interfaces, the
implementations, and the DDL produced by the
SQL Maven plugin all agree on
the schema.
Like its sibling, this module is built on the Wurbelizer engine: each
generator is a .wrbl template that the wurbelizer-maven-plugin compiles into a Java class and runs at build
time, weaving the generated text into guarded regions of the application's source files (the
//GEN-BEGIN:<tag> … //GEN-END:<tag> fold blocks that follow a // @wurblet <tag> <Name> anchor). The
developer keeps full control of the surrounding class; the wurblet owns only the fenced block and regenerates it on
every build.
Interface side vs. implementation side¶
tentackle-wurblets |
tentackle-persistence-wurblets (this module) |
|
|---|---|---|
| Generates into | PDO / operation interfaces, DTOs | PDO / operation implementation classes (and some interface anchors) |
| Typical output | CN_*/AN_* constants, getter/setter declarations, relation method declarations, unique-domain-key types |
field declarations, SELECT/INSERT/UPDATE/DELETE code, class variables, caches, remote delegates |
| Base wurblet class | ModelWurblet |
DbModelWurblet (extends ModelWurblet) |
Because this module depends on tentackle-wurblets, every helper available to the interface wurblets is also
available here, extended with persistence-specific machinery — most importantly the translation of the
wurblet-argument query language
into real SQL WHERE/ORDER BY/join clauses and the JDBC parameter binding that goes with it.
Why generate the persistence implementation?¶
A Tentackle entity's persistence implementation is almost entirely mechanical but highly repetitive and
error-prone if written by hand: for every query you need a SELECT string assembled from the right column-name
constants, a PreparedStatement whose parameters are bound in the correct order with the correct
DataType setter, a cached statement id, dialect-aware
paging/locking, aggregate component loading, and — when remoting is enabled — a matching remote delegate. Deriving
all of this from the model guarantees it stays correct as the model evolves, supports every target
backend at once, and frees the developer to write only the
genuinely custom logic outside the guarded regions.
Design principles¶
- Model-driven and backend-aware. Every statement is built from the model and the active backends; the same template emits correct SQL for PostgreSQL, Oracle, DB2, etc., and refuses to generate features a target backend doesn't support.
- Woven into the developer's classes. Generated implementation code lives in guarded regions, so a persistence class can freely mix generated queries with handwritten methods.
- Statement caching by construction. Generated queries register prepared-statement ids in the entity's class variables, so the runtime reuses statements without per-call string building.
- Transparent remoting. When an entity or method is remote-enabled, the wurblets generate both the local implementation and the TRIP remote-delegate code, so the same call works whether the JVM is directly connected to the database or talking to a server.
DbModelWurblet — the shared base¶
DbModelWurblet extends
ModelWurblet and is the
base class for every wurblet in this module (pulled in via
header.incl's @{extends DbModelWurblet}). On top of
the model loading and target resolution it inherits, it adds:
- Tracking and context options parsed in
run():--context=<key>(domain-context column),--tracked/--attracked/--fulltracked/--untracked(how modified state is tracked on the returned objects), exposed viaisTracked(),getContextAttribute(), etc. - Persistability guards —
assertEntityIsPersistable()andassertEntityNotEmbedded()make a wurblet fail cleanly when it is applied to an entity that cannot be queried directly. - The query-expression model — after parsing the
wurblet arguments,
it exposes the parts of a query:
getExpression()/getExpressionArguments()(theWHEREtree),getExtraArguments()(attributes to update),getSortingArguments()/getDefaultSorting()(ordering), andgetJoinPaths()/isWithJoins()/isWithFilteredJoins()(eager-loading joins). - SQL and JDBC code builders used inside the templates:
createRelopSql()/createRelopCode()(turn a condition into SQL and into the Java that appends it),createWhereSetPars()/createJoinSetPars()(bind statement parameters in order),createOrderBy(),createJoins(),createStatementId(),buildMethodParameters()/buildInvocationParameters()(the generated method signatures and call sites), andcreateJdbcSetterName()/createJdbcGetterName()(pick the rightResultSet/PreparedStatementaccessor for aDataType).
Two @{config ...}@ flags adjust how a template's arguments are parsed: pathAllowed (the WHERE expression may
follow relation paths) and groupArgs (the update wurblets accept a second group of <values> after a |).
The Wurblets¶
All templates live under src/main/wurblets/org/tentackle/persist/wurblet and carry their own usage
documentation in their leading @{comment ...}@ block. They fall into four groups.
Structural — declarations, class variables, and accessors¶
| Wurblet | Generates |
|---|---|
Declare |
Field declarations for the entity's attributes. Options: --protected, --annotations, --mock. |
ClassVariables |
The per-class PersistentObjectClassVariables holder (table alias, statement-id registry, super/sub-class wiring, eager relations) shared by all instances of the entity. |
MethodsImpl |
Getter/setter and other method implementations. Options: --noif (no interface ⇒ no @Override), --noudk, --noisroot, --mock. |
MethodCache |
The PdoMethodCache for an embedded entity. |
Query — select, update, delete, reference checks¶
These come in a Db… (low-level persistent object) flavor and a Pdo… (full PDO) flavor. All take a
wurblet-argument <expression>
and share the options --private, --append=<sqltext> and --classvar=<classvariables>; the Pdo… variants add
--noif.
| Wurblet | Generates | Notable extra options |
|---|---|---|
DbSelectList / PdoSelectList |
A method selecting a list of objects. | --sort, --lock, --limit, --offset, --resultset, --bounded; PDO adds --cursor, --immutable, --finallyImmutable, --mutable |
DbSelectUnique / PdoSelectUnique |
A method selecting a single object. | --sort, --lock; PDO adds --tokenlock, --immutable, --finallyImmutable, --mutable |
DbUpdateBy / PdoUpdateBy |
A bulk-update method. Takes <expression> \| <values> (the groupArgs config). |
|
DbDeleteBy / PdoDeleteBy |
A bulk-delete method. | |
DbIsReferencing / PdoIsReferencing |
A method checking whether an object is referenced by others (referential-integrity guard). |
The select wurblets have their own document; the reference check and the two modifying wurblets deserve a closer look here.
PdoIsReferencing — the referential-integrity guard¶
PdoIsReferencing generates a boolean existence check: a SELECT of the id column with
the given WHERE expression, limited to a single row. No objects are loaded and nothing is
modified — the method merely answers whether at least one matching row exists.
Its main job is guarding deletion. For every non-composite object relation whose target entity has
application-checked integrity, PdoRelations generates an
isReferencing<Entity>By<attribute> anchor together with the corresponding referencing-class
registration, so that isReferenced() — and with it the default isRemovable() — can ask every
registered referencing entity whether it still points to the object before it is deleted.
Handwritten anchors work the same way for custom guards (see for example isReferencingUser in
ModificationLog).
All attributes of the expression must belong to the same entity.
The rich desktop client builds on this as
well: PdoEditor.isRemoveAllowed() consults isRemovable() — and thus isReferenced() — to
enable or disable the delete button, so removing a still-referenced PDO is already inhibited at
the GUI level instead of failing later with an SQL exception raised by a foreign-key constraint.
PdoUpdateBy and PdoDeleteBy — bulk writes¶
PdoUpdateBy generates a method executing a single UPDATE <table> SET <values> WHERE <expression>,
PdoDeleteBy a single DELETE FROM <table> WHERE <expression>; both return the number of affected
rows. The class-level write permission is still checked (see
security), the domain-context condition
(--context) still applies, and both are remote-capable like every other query wurblet.
Warning — bulk writes bypass the DDD aggregate rule. These methods modify rows directly, without instantiating the PDOs. Everything the regular PDO write path guarantees is skipped:
- No optimistic locking. The
serialof the affected rows is neither checked nor incremented. A concurrent user editing one of the objects will neither detect the bulk change, nor will their subsequent save be rejected — the bulk change is silently overwritten, the classic lost update.- No token locks. Rows token-locked by other users are updated or deleted regardless.
- No modification tracking. Neither a tableserial bump nor a modification-log entry is written, so PDO caches — local and in other JVMs — are never invalidated and keep serving stale objects.
- No aggregate semantics. A bulk delete removes only rows of this table: components are not cascaded (leaving orphans when applied to a root entity),
isReferenced()is not consulted, and no domain logic or validation runs.In a Tentackle application, all changes normally go through the aggregate root as full PDO operations.
PdoUpdateByandPdoDeleteByexist for rare corner cases where performance is critical — mass operations on tables that are untracked, uncached, unlocked, and free of components and referencing entities (typically log- or queue-like data). Anywhere else, using them means you are responsible for consciously re-establishing the guarantees above.
Relations and caching¶
| Wurblet | Generates |
|---|---|
PdoRelations |
The relation handling for an entity: loadComponents(), insertPlainWithComponents(), deletePlainWithComponents(), the referencing-class registration, and anchors for the per-relation PdoSelect…/PdoDelete…/PdoIsReferencing wurblets. Toggle blocks with --[no]loadcomponents, --[no]insertcomponents, --[no]deletecomponents, --nori, --noselects, --nodeletes, --novalidate, --mock. |
PdoCache |
A PdoCache for the entity. Options select the strategy (--strategy=PRELOAD\|LRU\|LFU\|FORGET), --maxsize, --keepquota, --mutable, --udk and additional unique (optionally filtered, method-extracted) indexes. |
Remoting¶
| Wurblet | Generates |
|---|---|
AssertRemote |
Verifies the entity's remote delegate interface/implementation exist, generating them from templates if missing (a no-op when remoting is disabled). |
RemoteMethod |
The remote-delegate invocation code for a method annotated @RemoteMethod. Options: --this (pass the PDO itself), --nocontext (omit the DomainContext), --instance=<object> (target object on the server). |
Shared template fragments¶
The .incl files factor out repeated generation logic shared by several query wurblets:
genwhere.incl (the WHERE clause, including the
class-id condition for single-table inheritance and the domain-context predicate),
genupdate.incl (the SET list of an update), and
gensetpar.incl (binding statement parameters,
including join and context parameters). header.incl is
the shared preamble with the imports and @{extends DbModelWurblet}.
Supporting Java Classes¶
| Class | Role |
|---|---|
DbModelWurblet |
Base class for all persistence wurblets. |
WhereClauseGenerator |
A CodeGenerator that walks the parsed argument expression and emits the Java that builds the SQL WHERE clause. |
JoinClauseGenerator |
Emits the SQL for eager-loading joins, including filtered joins. |
RemoteIncludes |
Creates and updates the generated TRIP remote interface/implementation files (from FreeMarker templates) used by AssertRemote. |
RemoteMethodHelper |
Extracts the wurblet tags for a @RemoteMethod from the compiled class file. |
How It Fits Together¶
model (*.map / entity definitions) tentackle-sql (DataType, Backend)
│ │
└───────────────┬───────────────────────┘
▼
tentackle-wurblets ──► PDO / operation *interfaces*, DTOs,
(interface side) constants, unique-domain-key types
│
▼ (depends on)
tentackle-persistence-wurblets ──► PDO / operation *implementations*:
(this module, implementation side) field declarations, class variables,
SELECT/INSERT/UPDATE/DELETE, caches,
remote delegates (TRIP)
During an application build, the
Tentackle Maven plugin arranges for
the wurbelizer plugin to run both wurblet sets over the sources, in phases, so the interface anchors are filled
before the implementation anchors that reference them. A typical generated persistence class therefore contains a
Declare block, a ClassVariables block, a MethodsImpl block, a PdoRelations block (which in turn nests
PdoSelect…/PdoDelete… anchors), and — when the model marks the entity cacheable or remote — PdoCache and
AssertRemote/RemoteMethod blocks.
Module Dependencies¶
- Wurbelizer (
org.wurbelizer:wurbelizer) — the template engine and the weaving of guarded regions. tentackle-wurblets— the interface-side wurblets and their shared baseModelWurblet, the wurblet-argument parser and the model/SQL helpers this module builds upon.tentackle-build-support— shared build-time utilities (and FreeMarker template support for remote code).- Transitively,
tentackle-modelandtentackle-sqlfor the model andDataType/BackendAPIs.
Like tentackle-wurblets, this is a build-time only module, packaged with an Automatic-Module-Name of
org.tentackle.persist.wurblet; it runs on the wurbelizer plugin's classpath and is never shipped inside the
running application.
Related Documentation¶
- Tentackle Wurblets — the interface-side counterpart, the wurblet anchor mechanism, and the wurblet-argument query language.
PdoSelectList/PdoSelectUnique— generating finder methods, including eager-loading*load joins.- Eager Relations — model-level
select = eagervs. domain-level load joins, and why eager loading never cascades. - Model Definition — the model the wurblets consume.
- PDO — Persistent Domain Objects — the objects these implementations realize.
- Tentackle SQL —
DataType/Backendused to build the SQL and bind parameters. - TRIP — the remoting protocol behind the generated remote delegates.
- Tentackle Maven Plugin — drives wurblet execution during the build.
- Wurbelizer — How it works — the underlying code-generation engine.