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).
- Models (
- Uninstrumented Core: All query and persistence capabilities are accessible via the instance-based query API (
ModelQueryandEntityQuery) 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()orwrite()call acquires its connection from theDataSourceand commits/rolls back and closes it. - A nested call joins the active transaction, reusing the open connection.
- A top-level
- 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:
| Method | Behavior |
|---|---|
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()andvalidate()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
| Dimension | Supported Version | Notes |
|---|---|---|
| Java Runtime | Java 21 LTS | Verified on Eclipse Adoptium Temurin 21 |
| Database | PostgreSQL 12, 13, 14, 15, 16+ | Uses standard PostgreSQL JSON, array, enum, and advisory locks |
| Instrumentation Plugin | 2.1.2 | Version-aligned with rathorm:2.1.2 |
| Build Tool | Apache 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