Ujorm3: A New Lightweight ORM for JavaBeans and Records
Source: Dev.to
Why a New ORM?
The original JDBC API was never meant to be a full‑featured ORM, and over the years a plethora of libraries and frameworks have sprung up, each with its own trade‑offs in complexity, performance, and usability.
Ujorm 3 aims to be a reliable, safe, efficient, and easy‑to‑understand tool for relational‑database access:
- Thin layer over JDBC – no hidden magic.
- Stateless objects and native SQL – you stay in full control of what is sent to the database.
- No SQL‑dialect handling – write plain SQL; the library only adds type‑safe mapping helpers.
- No query‑result caching – only a small amount of metadata is retained for speed.
The first release candidate (RC1) is available on Maven Central under the Apache License 2.0.
Core Concepts
| Concept | Description |
|---|---|
SqlQuery (formerly SqlParamBuilder) | Facade over PreparedStatement. Supports named parameters, eliminates checked exceptions, and returns a Stream for SELECTs. |
ResultSetMapper | Maps a ResultSet to domain objects. The mapping model is built once (on first use) and reused, dramatically reducing per‑query overhead. |
| Annotations | Uses standard jakarta.persistence annotations (@Table, @Column, @Id). The library can also infer mappings when annotations are absent. |
| Supported POJOs | Both mutable JavaBeans and immutable records are fully supported. |
| Relations | Only M:1 (many‑to‑one) relations are supported. 1:M collections are deliberately omitted to avoid hidden queries and N+1 problems. |
| Column Labels | Columns can be referenced by a dot‑chained label (e.g. "city.name"). For safety, a generated type‑safe metamodel (Meta* classes) is preferred. |
EntityManager | Provides CRUD operations (including batch commands) via a Crud object. Supports partial updates by enumerating columns or by diffing the original object. |
Example: Selecting Employees
// Static mapper – thread‑safe and reusable
static final ResultSetMapper EMPLOYEE_MAPPER =
ResultSetMapper.of(Employee.class);
void select() {
var sql = """
SELECT ${COLUMNS}
FROM employee e
JOIN city c ON c.id = e.city_id
LEFT JOIN employee b ON b.id = e.boss_id
WHERE e.id > :employeeId
""";
var employees = SqlQuery.run(connection(), query -> query
.sql(sql)
// Map columns – the ${COLUMNS} placeholder is replaced automatically
.column("e.id", MetaEmployee.id)
.column("e.name", MetaEmployee.name)
.column("c.name", MetaEmployee.city, MetaCity.name)
.column("c.country_code", MetaEmployee.city, MetaCity.countryCode)
.column("b.name", MetaEmployee.boss, MetaEmployee.name)
.bind("employeeId", 0L)
// Convert the ResultSet stream to a List
.streamMap(EMPLOYEE_MAPPER.mapper())
.toList());
}The domain class does not need prior registration.
If you prefer to keep the SQL text untouched, use label() instead of column(). **column() and label() cannot be mixed in the same query.
Benchmark Highlights
- Runtime‑generated mapping classes (instead of reflection) → lower memory usage, less GC pressure.
- Zero external dependencies → the compiled benchmark + Ujorm 3 JAR is:
org.ujorm
ujorm-orm
3.0.0-RC1Generating the Metamodel (optional)
If you want the type‑safe Meta* classes, enable the annotation processor in the maven-compiler-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.ujorm</groupId>
<artifactId>ujorm-processor</artifactId>
<version>3.0.0-RC1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>Summary
Ujorm 3 delivers a minimal‑overhead, transparent, and type‑safe way to work with relational databases in Java. By staying close to JDBC while providing a clean, modern API, it lets developers write clear, performant code without the hidden costs of heavyweight ORMs.
Give it a try in your next Java 17+ project and experience the simplicity of “the simplest thing that could possibly work.”
<dependency>
<groupId>org.ujorm</groupId>
<artifactId>ujorm-meta-processor</artifactId>
<version>3.0.0-RC1</version>
</dependency>The Ujorm module from the Benchmark project can be used as a template for a sample implementation. The library’s codebase is currently covered by JUnit tests that utilize an in‑memory H2 database (in addition to mocked objects).
Before releasing the final version, I plan to add integration tests for PostgreSQL, MySQL, Oracle, and MS SQL Server databases.
Note:
If you are working for a corporate client expecting standards or portability of abstractions between databases, use JPA/Hibernate instead. If you have already found an ORM framework that meets your expectations and needs, stick with it. However, if you are looking for a fast and transparent alternative without hidden mechanisms for your new project, the Ujorm 3 library is definitely worth a try.
Project Homepage
(link to the project’s homepage can be inserted here)
More Examples as a JUnit Test
(link or description of additional JUnit test examples can be inserted here)
Benchmark Tests
(link or description of benchmark tests can be inserted here)