Skip to content

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:

  • Dba persistence session: the implementation of Session that owns transactions, connections, prepared statements, id-sources, and statistics.
  • AbstractDbObjecta 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 ConnectionManager that 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 Db does not own a JDBC connection directly; it asks a ConnectionManager to 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/AbstractDbOperation hold 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's PersistenceException hierarchy, classified via the Backend, 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).
  • MpxConnectionManagermultiplexing 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 and SQLExceptionPersistenceException translation.
  • ManagedConnectionMonitor — watches managed connections; DefaultReconnectionPolicy decides 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)) — creates Db sessions; it is the local implementation of the session factory contract.
  • DbPool — a SessionPool of Dbs for one SessionInfo. With the default connection manager each pooled Db is one physical connection; with the multiplexing manager pooled Dbs are virtual connections attached only during operations — the preferred server configuration.
  • MultiUserDbPool — a pool of DbPools, one per SessionInfo, for multi-user servers.
  • DbRemoteSessionFactory (@Service(RemoteSessionFactory.class)) and RemoteSessionAdapter provide 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/translate SQLExceptions, and guard against accidental reuse.
  • ResultSetWrapper — an AutoCloseable wrapper over a ResultSet; ResultSetSection / ResultSetSkipBlock support 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-specific SELECT/LIMIT/OFFSET automatically.
  • DbBatch / DbBatchStatement — JDBC batching of standard prepared statements, grouped per root-entity type to preserve referential integrity, flushed at savepoints/commit. Enabled via the batchsize backend property; off by default and to be used with care because it complicates error diagnosis.

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 (table modification) and ModificationTally — the persisted per-table modification serial and its in-memory counter (local sessions only).
  • TableSerialHistory and TableSerialExpirationBacklog — 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

Utilities (SPI replacements)

  • DbUtilities (@Service(DbUtilities.class)) — persistence helpers; the clean seam between the low- and high-level layers (the PDO layer replaces it with PersistenceUtilities).
  • DbSessionUtilities (@Service(SessionUtilities.class)) — extends the session utilities to map SQLExceptionPersistenceException.

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 — the Preferences implementation; nodes persist via DbPreferencesNode and key/value pairs via DbPreferencesKey; changes are flushed through DbPreferencesOperation and node/key events propagate to all JVMs.
  • DbPreferencesFactory (@Service(PersistedPreferencesFactory.class)) — a drop-in replacement for the JRE preferences factory, normally reached via PersistedPreferencesFactory.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 the service=<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 by RemoteDbSessionCleanupThread.

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.


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.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 (the Session contract it implements) and tentackle-sql (the dialect-aware Backend it executes), plus java.naming.
  • It bundles the PostgreSQL JDBC driver for tests; production drivers are supplied by the application.
  • It opens org.tentackle.dbms and org.tentackle.dbms.prefs to org.tentackle.core for TRIP serialization.
  • It provides a ModuleHook service (org.tentackle.dbms.service.Hook) for i18n.
  • Through @Service, it replaces several lower-layer singletons (SessionFactoryDbFactory, ModificationTrackerDbModificationTracker, SessionUtilitiesDbSessionUtilities, DiagnosticUtilitiesDbDiagnosticUtilities, PersistedPreferencesFactoryDbPreferencesFactory); the PDO layer above in turn replaces DbUtilities/DbClassVariablesFactory with PDO-aware versions.
  • Session — the Session/transaction/tracking contracts this module implements.
  • 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 *.trip delegates.
  • 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.