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