Tentackle Database — The Low-Level Persistence Engine¶
Overview and Motivation¶
The tentackle-database module (Java package org.tentackle.dbms) is Tentackle's low-level persistence
engine — the runtime that actually moves data between Java objects and a relational database. It sits between
the dialect-aware tentackle-sql backend below it and the
PDO-aware tentackle-persistence layer above
it, and it provides the concrete implementation of the Session
abstraction.
Its two central classes are:
Db— a persistence session: the implementation ofSessionthat owns transactions, connections, prepared statements, id-sources, and statistics.AbstractDbObject— a persistent low-level database object: the base class every persistence implementation extends, providing generic insert/update/delete/select machinery driven by entity-specific code that the Wurbelizer generates from the model.
The defining feature of this layer is 2-tier / n-tier transparency. A Db session is an abstraction over a
connection to a "server", where server need not be a database server at all:
- A local session talks to a database backend through a
ConnectionManagerthat owns the physical JDBC connection. This is what client apps in 2-tier mode and application servers use. - A remote session is connected to a Tentackle application server over TRIP. This is what n-tier (n ≥ 3) clients use.
Because both look identical to the code above, a Tentackle application switches between 2-tier and n-tier deployment by changing a config file, not the source code.
Design principles¶
- Session over connection, not session = connection. A
Dbdoes not own a JDBC connection directly; it asks aConnectionManagerto attach one for the duration of a task and detach it afterward. This decoupling is what enables connection multiplexing in servers. - Generated specialization, generic engine.
AbstractDbObject/AbstractDbOperationhold all the generic persistence logic; the per-entity SQL and column mapping are generated (by wurblets) into the application's subclasses. The OO "template method" approach keeps the framework code entity-agnostic. - Location transparency via TRIP delegates. Every persistence capability has a remote-delegate twin in
org.tentackle.dbms.trip, so the same calls run locally or against a remote server. - SQLExceptions never leak. Statement/result wrappers and the session utilities translate vendor
SQLExceptions into Tentackle'sPersistenceExceptionhierarchy, classified via theBackend, so upper layers never see raw JDBC errors. - Replaceable singletons via SPI. Utilities, factories, and the modification tracker are
@Service-registered so the PDO layer above can swap in PDO-aware versions transparently.
Key Concepts¶
Db — the session¶
Db implements Session (from
tentackle-session) and is the workhorse of the module.
Each thread must use its own Db. Its responsibilities include:
| Group | Examples | Purpose |
|---|---|---|
| Connection brokering | getConnectionManager, attach/detach (internal), isRemote |
Acquire/release a managed connection for a unit of work. |
| Transactions | begin, commit, rollback, createTransaction, savepoints |
Run (nestable) transactions; the voucher protocol from Session decides who really commits. |
| Statements | getPreparedStatement, createStatement |
Obtain cached, wrapped statements bound to the session's connection. |
| Id sources | the per-session IdSource (ObjectId / ObjectSequenceId) |
Allocate unique object IDs. |
| Batching | executeBatch, batch configuration |
Collect prepared statements into JDBC batches for throughput. |
| Modification tracking | the session's ModificationTracker integration |
Advance table serials on writes for caches/notifications. |
| Diagnostics | statement history, statement/transaction statistics, the statement tracer | Observe and profile database activity. |
| Keep-alive / lifecycle | isAlive, reconnection policy |
Survive idle timeouts and dropped connections. |
A Db can be cloned and pooled, and exposes whether it is local or remote so the rest of the framework
can adapt where it genuinely matters (rarely).
AbstractDbObject and AbstractDbOperation¶
AbstractDbObject<P> is the base of all persistent objects.
It is associated with a Session (local or remote) and provides the generic CRUD engine; entity specifics
(table/column mapping, finders, relations) are generated by wurblets into subclasses according to the
model. It carries the bookkeeping every
persistent row needs — object id, serial (optimistic locking), table serial (cache expiry) — and is
@TripSerializable so instances can travel between JVMs.
AbstractDbOperation<P> is the counterpart for operations
— units of work that are not a single row but a (often complex, transactional) procedure bound to a Db session,
and are likewise remoting capable.
Per-class static metadata lives in DbObjectClassVariables
and DbOperationClassVariables, created via the
DbClassVariablesFactory (which the PDO layer replaces
with a PDO-aware factory).
Connection management¶
JDBC connections are never used directly. A session borrows one from a ConnectionManager via attach, uses
it for a transaction/one-shot, then detaches it — letting the manager broker N sessions over M connections.
ConnectionManager— the broker interface (login/logout,attach/detach/forceDetach,getMaxConnections, …).DefaultConnectionManager— strict 1:1 mapping (each session keeps its own physical connection).MpxConnectionManager— multiplexing manager that maps N sessions onto M < N connections, transparently to the application; the typical choice for application servers with many clients. (Note: this is not session pooling — multiplexing is invisible to the app.)ManagedConnection— a JDBC connection wrapped with a prepared-statement cache andSQLException→PersistenceExceptiontranslation.ManagedConnectionMonitor— watches managed connections;DefaultReconnectionPolicydecides how to reconnect after a failure.
Connection managers are not used by remote sessions (those go over TRIP instead).
Session creation and pooling¶
DbFactory(@Service(SessionFactory.class)) — createsDbsessions; it is the local implementation of the session factory contract.DbPool— aSessionPoolofDbs for oneSessionInfo. With the default connection manager each pooledDbis one physical connection; with the multiplexing manager pooledDbs are virtual connections attached only during operations — the preferred server configuration.MultiUserDbPool— a pool ofDbPools, one perSessionInfo, for multi-user servers.DbRemoteSessionFactory(@Service(RemoteSessionFactory.class)) andRemoteSessionAdapterprovide the remote-session side.
Id sources¶
IdSource supplies unique object IDs (normally one instance per
session). Two implementations ship:
ObjectId— used when the backend has no sequences; updates a counter table, optimized to write only once at the end of transaction.ObjectSequenceId— uses a database sequence, multiplied by a configurable factor with an in-range counter to cut round-trips.
AbstractIdSource is the shared base; selection/configuration
goes through IdSourceConfigurator /
DefaultIdSourceConfigurator.
Statements, queries and result sets¶
StatementWrapper/PreparedStatementWrapper— wrap JDBC statements, catch/translateSQLExceptions, and guard against accidental reuse.ResultSetWrapper— anAutoCloseablewrapper over aResultSet;ResultSetSection/ResultSetSkipBlocksupport joines (due to inheritance or for load joins).Query— builds the SQL string + parameter set for a one-off query (e.g., user-entered), adding the backend-specificSELECT/LIMIT/OFFSETautomatically. All predefined datatypes are accepted as parameters, as well as application-specific types with a registeredDataType. Notice that some datatypes span more than one column (BMoneyorZonedDateTime, for example) and thus require a matching number of question marks.- The
org.tentackle.dbms.dslpackage builds such aQueryfrom typed expressions instead of hand-written SQL strings, and counts the columns of multi-column datatypes for you — see The Query DSL. DbBatch/DbBatchStatement— JDBC batching of standard prepared statements, grouped per root-entity type to preserve referential integrity, flushed at savepoints/commit. Enabled via thebatchsizebackend property; off by default and to be used with care because it complicates error diagnosis. See SQL Statement Batching.
Transactions¶
AbstractTransaction is the serializable base;
DbTransaction holds local transaction state and
RemoteTransaction the remote variant.
DbTransactionFactory creates them and
DbTransactionHandle is the caller's handle.
TransactionStatistics collects per-transaction metrics.
Modification tracking and table serials¶
This layer implements the change-tracking contracts from tentackle-session:
DbModificationTracker(@Service(ModificationTracker.class)) — the concrete tracker; advances serials when data changes and notifies listeners (e.g., caches).DbModification(tablemodification) andModificationTally— the persisted per-table modification serial and its in-memory counter (local sessions only).TableSerialHistoryandTableSerialExpirationBacklog— track committed inserts/updates/deletes in memory so caches can be expired precisely, filling the gaps left by deletions and rolled-back changes and avoiding needless cache invalidation. Maintained only by the server connected to the database; intermediate servers and clients fetch their expiration sets remotely.DbModificationType/ModificationType/ModificationTypeFactory— enumerate INSERT/UPDATE/DELETE (and friends).
Modification logging¶
ModificationLog (table modlog) records object modifications
for asynchronous database coupling, higher-level replication, audit, etc. Objects opt in via
ModificationLoggable; logs are produced through
ModificationLogFactory /
DefaultModificationLogFactory.
Persistence visitors¶
PersistenceVisitor is a reflective hook registered on a
Db (valid for one transaction) that is consulted before each INSERT/UPDATE/DELETE to decide whether the
operation is allowed or should be silently skipped — e.g.
IgnoreDuplicatesPersistenceVisitor.
Diagnostics and statistics¶
StatementTracer— on-demand SQL tracing (off by default for performance), withStatementTraceException.StatementStatistics/StatementStatisticsResultandStatementHistory— collect and report statement usage.DbDiagnosticUtilities(@Service(DiagnosticUtilities.class)) — adds persistence info to thread stack dumps.
Utilities (SPI replacements)¶
DbUtilities(@Service(DbUtilities.class)) — persistence helpers; the clean seam between the low- and high-level layers (the PDO layer replaces it withPersistenceUtilities).DbSessionUtilities(@Service(SessionUtilities.class)) — extends the session utilities to mapSQLException→PersistenceException.
Database-backed Preferences¶
The org.tentackle.dbms.prefs package implements the standard java.util.prefs.Preferences API with the
database as the backing store, so application preferences are shared across all JVMs rather than living in a
local registry/file:
DbPreferences— thePreferencesimplementation; nodes persist viaDbPreferencesNodeand key/value pairs viaDbPreferencesKey; changes are flushed throughDbPreferencesOperationand node/key events propagate to all JVMs.DbPreferencesFactory(@Service(PersistedPreferencesFactory.class)) — a drop-in replacement for the JRE preferences factory, normally reached viaPersistedPreferencesFactory.getInstance().prefs/trip— the remote delegate so preferences work over a remote connection.
Remoting (TRIP)¶
The org.tentackle.dbms.trip package makes the whole layer location transparent. The connection/handshake
chain is:
TripServer— the generic application server; parses theservice=<uri>backend properties and creates the TRIP transports that accept client connections.RemoteDbConnection— the first remote object handed to a connecting client; handles login to a session.RemoteDbSession— the server-side session that creates all other delegates for the client; cleaned up byRemoteDbSessionCleanupThread.
Each capability is exposed as a remote delegate (interface + …Impl executor):
| Delegate / impl | Mirrors |
|---|---|
DbRemoteDelegate / …Impl |
Db (the session itself) |
AbstractDbObjectRemoteDelegate / …Impl |
AbstractDbObject CRUD/queries |
AbstractDbOperationRemoteDelegate / …Impl |
AbstractDbOperation |
DbModificationTrackerRemoteDelegate / …Impl |
modification tracking / serials |
ModificationLogRemoteDelegate / …Impl |
modification logs |
RemoteDbDelegateLocator /
DbRemoteDelegateLocator
(@Service(RemoteDbDelegateLocator.class)) find the right delegate for a class; the invocation handlers
(DbRemoteInvocationHandler,
RemoteDbDelegateInvocationHandler) and
RemoteDbSessionInvocationFilter route and
guard the calls. SQLExceptionComponentProvider
/ SQLExceptionInstanceCreator ensure JDBC
exceptions serialize correctly across the wire, and DefaultMasterSerial
carries the master modification serial back to clients.
Preloading remote delegates¶
Delegates are created lazily: the first time a class needs one, Db.getRemoteDelegate(int) asks the
server for it — one network roundtrip per class. For an application with several hundred entities
running on a link with a high latency, these roundtrips accumulate and dominate the perceived
startup time.
A class can be marked as a candidate for preloading with
@PreloadRemoteDelegate:
@PersistentObjectService(User.class)
@PreloadRemoteDelegate
public class UserPersistenceImpl extends AbstractPersistentObject<User, UserPersistenceImpl> { ... }
The annotation is meta-annotated with @Service, so the build
collects the annotated class names in META-INF/services/org.tentackle.dbms.trip.PreloadRemoteDelegate.
The client reads them as plain strings via DbUtilities.getPreloadDelegateClassNames() — the classes
themselves are never loaded — and fetches all of their delegates in a single roundtrip using
RemoteDbSession.getRemoteDelegates(String[]):
Session session = Pdo.createSession(sessionInfo);
session.preloadRemoteDelegates(); // one roundtrip instead of one per class
Preloaded delegates are consumed transparently: getRemoteDelegate(int) takes them from the preload
map instead of going to the server. Preloading is remembered for the session, so it is repeated on
reOpen() and after an automatic reconnection. A cloned session is a session of its own and does
not inherit the request — it must invoke preloadRemoteDelegates() itself if it wants to.
Two rules apply:
- The application decides, per session. Nothing is preloaded unless
preloadRemoteDelegates()is invoked. Not every session benefits — preloading costs one roundtrip plus one server-side delegate per candidate, which is wasted on short-lived or special-purpose sessions. - Annotate the classes used at startup, not the whole entity model, and never a class whose
delegate changes the state of the server session when created.
DbModificationTrackeris such an example: creating its delegate marks the session as the modification tracker's session, so it must not be a preload candidate.
Class names the server cannot resolve are answered with null and logged there — preloading never
breaks the session. Since getRemoteDelegates is part of the RemoteDbSession interface, client and
server must be built from the same Tentackle version, which the login handshake verifies anyway.
Package Layout¶
| Package | Contents |
|---|---|
org.tentackle.dbms |
The engine: Db, AbstractDbObject/AbstractDbOperation, connection managers and ManagedConnection, pools and factories, id-sources, statement/query/result wrappers, transactions, batching, modification tracking + table serials, modification logging, persistence visitors, diagnostics/statistics and the SPI utility replacements. |
org.tentackle.dbms.dsl |
The query DSL: typed tables, columns, expressions and conditions rendered to the SQL of a Query. |
org.tentackle.dbms.prefs (+ prefs.trip) |
Database-backed java.util.prefs.Preferences and its remoting. |
org.tentackle.dbms.trip |
The TRIP server, remote connection/session and the remote delegates that make the layer location-transparent. |
org.tentackle.dbms.service |
The Hook (ModuleHook) for i18n bundle resolution. |
How It Fits Together¶
A persistence operation flows through the layers as follows:
tentackle-persistence (AbstractPersistentObject — PDO-aware)
│ extends
▼
AbstractDbObject ──── runs against ──── Db (Session)
│
local ────────────┤──────────── remote
▼ │ ▼
ConnectionManager │ DbRemoteDelegate (TRIP)
│ │ ▼ (on server)
ManagedConnection │ …RemoteDelegateImpl runs the
│ │ same AbstractDbObject logic
Backend (tentackle-sql)│ against a local Db
│ │ │
JDBC ◄───────────┘ ▼
results via TRIP
For a local Db, AbstractDbObject builds SQL via the
Backend, borrows a ManagedConnection from the
ConnectionManager, executes through the statement wrappers, and maps the ResultSetWrapper back to objects. For a
remote Db, the call is routed by the delegate locator to a …RemoteDelegate; on the server the matching
…RemoteDelegateImpl runs the very same AbstractDbObject logic against its own local Db and returns the
results over TRIP. Either way every write advances a table serial in the DbModificationTracker, which caches and
remote clients consult (via the expiration backlog) to invalidate exactly the stale entries.
The per-entity classes that extend AbstractDbObject/AbstractDbOperation are generated by wurblets from the
model; this module is the generic runtime they
plug into.
Module Dependencies¶
tentackle-database sits in the middle of the persistence stack:
- It requires transitive
tentackle-session(theSessioncontract it implements) andtentackle-sql(the dialect-awareBackendit executes), plusjava.naming. - It bundles the PostgreSQL JDBC driver for tests; production drivers are supplied by the application.
- It opens
org.tentackle.dbmsandorg.tentackle.dbms.prefstoorg.tentackle.corefor TRIP serialization. - It provides a
ModuleHookservice (org.tentackle.dbms.service.Hook) for i18n. - Through
@Service, it replaces several lower-layer singletons (SessionFactory→DbFactory,ModificationTracker→DbModificationTracker,SessionUtilities→DbSessionUtilities,DiagnosticUtilities→DbDiagnosticUtilities,PersistedPreferencesFactory→DbPreferencesFactory); the PDO layer above in turn replacesDbUtilities/DbClassVariablesFactorywith PDO-aware versions.
Related Documentation¶
- Session — the
Session/transaction/tracking contracts this module implements. - The Query DSL — building projection queries from typed expressions instead of SQL strings.
- Tentackle SQL — the dialect-aware backend that builds the executed SQL.
- Persistence — the PDO-aware layer that builds on
AbstractDbObject/Db. - PDO — the persistent domain object pattern at the top of the stack.
- TRIP — the remoting protocol behind the
*.tripdelegates. - Model Definition — where the entities whose persistence code extends this module are declared.
- Services / ServiceFinder — the SPI mechanism behind the
@Service-replaced singletons and delegate discovery.