Skip to main content

RathORM

RathORM is a lightweight, synchronous, PostgreSQL-focused JDBC framework for Java 21+. It emphasizes predictable execution, explicit connection and transaction ownership, immutable metadata and query definitions, and single-traversal SQL compilation.


Key Principles​

  • Explicit Connection Ownership: Statements never guess connection lifetimes. Scopes clearly own or join transactions; externally supplied connections are never committed, rolled back, or closed.
  • Unified Execution & Codecs: A single execution and codec path (SqlExecutor) handles models, entities, raw queries, and batch operations with strict type mapping and parameter safety.
  • Separation of Concerns:
    • Models (Model): Writable single-table records with explicit dirty tracking, unknown-attribute rejection, and persistence lifecycle hooks.
    • Entities (Entity): Query projections supporting multi-table joins, declarative criteria, and efficient single-query nested relationship subqueries (json_agg).
  • Uninstrumented Core: All query and persistence capabilities are accessible via the instance-based query API (ModelQuery and EntityQuery) without bytecode instrumentation.
  • Optional Static Syntax: Subclass static helpers (User.findAll(), UserEntity.find(...)) can optionally be enabled via the deterministic Maven instrumentation plugin.
  • Read-Only Schema Planning: Schema synchronization inspects live PostgreSQL catalogs and generates inspectable, ordered migration plans with explicit destructive flags before any DDL executes.

Architecture and artifacts​

All runtime capabilities ship in one artifact, com.tranztechnologies:rathorm. Core, Spring, web, and export code retain separate Java packages so plain-Java usage does not initialize an adapter. Spring, servlet, CSV, scanning, and legacy collection libraries are optional dependencies selected only when those packages are used. rathorm-instrumentation remains a separate Maven plugin because it runs during the build. rathorm-examples is a non-published consumer test.


Installation​

Add the single runtime artifact to your Maven pom.xml:

<dependency>
<groupId>com.tranztechnologies</groupId>
<artifactId>rathorm</artifactId>
<version>2.1.2</version>
</dependency>

To enable optional static syntactic helpers on Model and Entity classes (User.findAll()), configure the build-time plugin:

<build>
<plugins>
<plugin>
<groupId>com.tranztechnologies</groupId>
<artifactId>rathorm-instrumentation</artifactId>
<version>2.1.2</version>
<executions>
<execution>
<goals>
<goal>instrument</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Quickstart (Plain Java)​

1. Initialize the Runtime​

Initialize RathOrmRuntime with standard javax.sql.DataSource and optional RuntimeConfig:

import com.tranztechnologies.rathorm.RathOrmRuntime;
import com.tranztechnologies.rathorm.RuntimeConfig;
import com.tranztechnologies.rathorm.QueryObserver;
import javax.sql.DataSource;

DataSource dataSource = ...; // Any DataSource (HikariCP, DBCP, DriverManagerDataSource)

RuntimeConfig config = RuntimeConfig.builder()
.queryTimeoutSeconds(30)
.defaultFetchSize(100)
.batchSize(500)
.queryObserver(QueryObserver.logging()) // Logs duration, row counts, and failures
.build();

RathOrmRuntime runtime = new RathOrmRuntime(dataSource, config);

2. Define a Writable Model​

Models represent individual database tables with writable persistence:

import com.tranztechnologies.rathorm.Model;
import com.tranztechnologies.rathorm.SqlType;
import com.tranztechnologies.rathorm.annotation.DbColumn;
import com.tranztechnologies.rathorm.annotation.TableInfo;

@TableInfo(tableName = "users")
public class User extends Model {
@DbColumn(type = SqlType.BIGINT, primaryKey = true, autoIncrement = true)
public static final String id = "id";

@DbColumn(type = SqlType.VARCHAR, length = 255, nullable = false)
public static final String email = "email";

@DbColumn(type = SqlType.VARCHAR, length = 100)
public static final String name = "name";
}

3. Define a Query Projection Entity​

Entities represent query projections and relationships:

import com.tranztechnologies.rathorm.Entity;
import com.tranztechnologies.rathorm.EntityTable;
import com.tranztechnologies.rathorm.annotation.BaseTable;

public class UserView extends Entity {
@BaseTable(alias = "u")
public static EntityTable<User> user = () -> columns(
column("u", User.id, "id"),
column("u", User.email, "email"),
column("u", User.name, "name")
);
}

4. Execute Queries and Transactions​

Use the instance-based query API (ModelQuery, EntityQuery) without bytecode instrumentation:

import com.tranztechnologies.rathorm.EntityQuery;
import com.tranztechnologies.rathorm.ModelQuery;
import com.tranztechnologies.rathorm.WhereCriteria;
import com.tranztechnologies.rathorm.OrderBy;
import java.util.List;
import java.util.Optional;

// 1. Read transactions with explicit connection ownership
List<UserView> users = runtime.read(() -> {
return EntityQuery.of(UserView.class)
.where(WhereCriteria.isNotNull("u.email"))
.orderBy(OrderBy.asc("u.name"))
.limit(25)
.list();
}).getResult();

// 2. Write transactions with automatic commit/rollback
runtime.write(() -> {
User user = new User();
user.set(User.email, "alice@example.com");
user.set(User.name, "Alice");
user.insert(); // Persists to database, hydrates generated ID
return user;
});

// 3. Single record lookups
Optional<User> found = runtime.read(() -> {
return ModelQuery.of(User.class)
.where(WhereCriteria.eq("id", 1L))
.first();
}).getResult();

Core Capabilities​

Explicit Transaction & Connection Ownership​

RathOrmRuntime provides deterministic transaction boundaries:

  • Owning vs. Joining:
    • A top-level read() or write() call acquires its connection from the DataSource and commits/rolls back and closes it.
    • A nested call joins the active transaction, reusing the open connection.
  • Rollback-Only: If any joined operation fails, the entire transaction is marked rollback-only and rolls back upon completion of the outer scope.
  • Externally Managed Connections:
    Connection externalConn = ...; // e.g. from Spring or another framework
    runtime.useConnection(externalConn, () -> {
    // Runs inside external connection scope; RathORM never commits or closes it
    return ModelQuery.of(User.class).list();
    });
  • Transaction Lifecycle Hooks:
    runtime.write(() -> {
    runtime.onCommit(() -> sendWelcomeEmail(user));
    runtime.onRollback(() -> recordFailureMetric());
    user.insert();
    return user;
    });

Query Execution Vocabulary​

Both ModelQuery<M> and EntityQuery<T> share an explicit, immutable query vocabulary:

MethodBehavior
list()Materializes all matching rows into a java.util.List.
first()Returns an Optional<T> of the first row, stopping immediately at the driver boundary (setMaxRows(1)).
single()Requires exactly one row; throws OrmException if zero or multiple rows match.
exists()Checks row presence directly (SELECT 1 ... LIMIT 1) without decoding columns.
count()Executes SELECT COUNT(*) FROM (...) computing total matching logical rows before pagination.
stream()Returns a caller-owned RowCursor<T> (AutoCloseable) reading rows lazily from the stream.

Single-Query Relationship Loading (@SubEntity)​

Nested child collections are declared via @SubEntity:

public class AccountWithEntries extends Entity {
@BaseTable(alias = "a")
public static EntityTable<Account> account = () -> columns(column("a", Account.id, "id"));

@SubEntity(conditions = "entries.account_id = a.id")
public Class<EntryView> entries = EntryView.class;
}

RathORM generates a single SQL query using PostgreSQL json_agg(...) subqueries, eliminating N+1 round-trips while preserving child base criteria and child ordering.


Diagnostics & Performance​

Query Observation​

Capture query durations, operations, row counts, and failures without leaking sensitive parameter values:

RuntimeConfig config = RuntimeConfig.builder()
.queryObserver(obs -> {
if (!obs.isSuccess()) {
logger.warn("Query failed [{}]: {} — Category: {}",
obs.duration(), obs.sql(), obs.failureCategory());
}
})
.build();

Categories include: CONSTRAINT_VIOLATION, SYNTAX_OR_ACCESS, QUERY_TIMEOUT, CONNECTION_FAILURE, TRANSACTION_ROLLBACK, DATA_EXCEPTION.

Bounded Result Caching (Optional)​

Result caching is disabled by default. When explicitly enabled, DefaultResultCache guarantees:

  • Database, schema, SQL, and parameter-aware cache keys.
  • Configurable size bounds (LRU eviction) and TTL expiration.
  • Complete defensive copying: entities are deeply snapshot on put and freshly instantiated on get.
  • Bypassed inside active write transactions to prevent dirty or stale reads.
  • Invalidation on transaction commit.
  • Strict isolation across runtime instances.
ResultCache cache = new DefaultResultCache("runtime-1", 500, 60_000L); // 500 entries, 60s TTL
RuntimeConfig config = RuntimeConfig.builder()
.resultCache(cache)
.build();

Schema Planning & Migration​

RathORM separates schema maintenance into an inspect → diff → plan → preview/validate → explicit apply pipeline:

import com.tranztechnologies.rathorm.RathORM;
import com.tranztechnologies.rathorm.SchemaPlan;
import com.tranztechnologies.rathorm.SchemaResult;
import java.sql.Connection;
import java.util.List;

Connection conn = dataSource.getConnection();

// 1. Inspect live database and compute an ordered migration plan (READ-ONLY)
SchemaPlan plan = RathORM.plan(conn, List.of(User.class, Role.class));

// Inspect operations before running
plan.operations().forEach(op -> {
System.out.println("SQL: " + op.sql() + " (Destructive: " + op.destructive() + ")");
});

// 2. Validate without executing DDL (READ-ONLY)
RathORM.validate(conn, List.of(User.class, Role.class));

// 3. Explicitly apply the approved plan
SchemaResult result = RathORM.apply(conn, plan);
if (result.isSuccess()) {
System.out.println("Schema synchronized successfully");
}
  • Safety Guarantee: plan() and validate() execute only read-only catalog SELECT queries and never modify the database.
  • Advisory Locks: apply() serializes concurrent executions using PostgreSQL advisory locks.

Optional integrations​

Adapter classes are already in rathorm. Add only the third-party API required by the integration. These dependencies are optional in RathORM's published POM, so a plain-Java consumer does not inherit Spring, servlet, or CSV libraries.

Spring integration​

Integrates with Spring's transaction synchronization and auto-configuration:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>

When @Transactional is active, RathORM operations automatically join the Spring transaction via runtime.useConnection(...), binding onCommit and onRollback to Spring's transaction synchronization.

Web integration​

Provides HTTP query parameter parsing with explicit field allowlists and bounded pagination:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
FieldAllowlist allowlist = FieldAllowlist.fromEntity(UserView.class);
WebQueryParser parser = new WebQueryParser(allowlist);
SqlQuery query = parser.parse(requestParams, UserView.class);

CSV export​

Streams query results to an Appendable using Apache Commons CSV:

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.14.1</version>
</dependency>
CsvExporter.exportEntities(UserView.class, new PrintWriter(response.getOutputStream()));

Verification Matrix & Support​

DimensionSupported VersionNotes
Java RuntimeJava 21 LTSVerified on Eclipse Adoptium Temurin 21
DatabasePostgreSQL 12, 13, 14, 15, 16+Uses standard PostgreSQL JSON, array, enum, and advisory locks
Instrumentation Plugin2.1.2Version-aligned with rathorm:2.1.2
Build ToolApache Maven 3.9+Managed wrapper ./mvnw provided

Development & Verification​

Run the verification suites from the repository root:

# 1. Single runtime artifact unit tests
./mvnw test

# 2. Install runtime, then verify and install the Maven plugin
./mvnw install
./mvnw -f rathorm-instrumentation/pom.xml install

# 3. Verify examples against the installed public artifacts
./mvnw -f rathorm-examples/pom.xml test

# 4. Whitespace and formatting check
git diff --check

# 5. Isolated PostgreSQL integration tests (requires PostgreSQL container/instance)
./mvnw -Ppostgres-it verify