DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Next-Level Persistence in Jakarta EE: How We Got Here and Why It Matters
  • Distributed Rate Limiting in Java: A Deep Dive into Bucket4j + PostgreSQL
  • Graceful Shutdown: Spring Framework vs Golang Web Services
  • Build a REST API With Just 2 Classes in Java and Quarkus

Trending

  • How to Build and Optimize AI Models for Real-World Applications
  • When Angular APIs Return 200 but the Frontend Is Already Failing Users
  • Production Database Migration or Modernization: A Comprehensive Planning Guide [Part 2]
  • We Went Multi-Cloud and Almost Drowned: Lessons From Running Across AWS, GCP, and Azure
  1. DZone
  2. Coding
  3. Frameworks
  4. Ujorm3: A New Lightweight ORM for JavaBeans and Records

Ujorm3: A New Lightweight ORM for JavaBeans and Records

Learn about Ujorm3, a new lightweight Java ORM for JavaBeans and Records. Enjoy type-safe native SQL mapping, zero reflection, and high performance.

By 
Pavel Ponec user avatar
Pavel Ponec
·
May. 19, 26 · Analysis
Likes (1)
Comment
Save
Tweet
Share
1.3K Views

Join the DZone community and get the full member experience.

Join For Free

"Do the simplest thing that could possibly work."

— Kent Beck, creator of Extreme Programming and pioneer of Test-Driven Development.

I believe the Java language architects didn't exactly hit the mark when designing the API for the original JDBC library for database operations. As a result, a significant number of various libraries and frameworks have emerged in the Java ecosystem, differing in their approach, level of complexity, and quality. 

I would like to introduce you to a brand-new lightweight ORM library, Ujorm3, which I believe beats the competition thanks to its simplicity, transparent behavior, and low overhead. The goal of this project is to offer a reliable, safe, efficient, and easy-to-understand tool for working with relational databases without hidden magic and complex abstractions that often complicate both debugging and performance. The final release is now available in the Maven Central Repository, released under the free Apache License 2.0.

The library builds on the familiar principles of JDBC but adds a thin layer of a user-friendly API on top. It works with clean, stateless objects and native SQL, so the developer has full control over what is actually executed in the database. Ujorm3 deliberately avoids implementing SQL dialects and instead uses native SQL complemented by type-safe tools for mapping database results to Java objects. It does not cache the results of any user queries. To achieve maximum speed, however, Ujorm3 retains certain metadata.

Application API Classes

The library offers a type-safe SelectQuery builder for constructing SQL SELECT queries smoothly in Java, while still fully supporting the classic SqlQuery for writing raw native SQL. Both approaches utilize the generated Meta classes for mapping and aliases, preventing SQL typos and ensuring compile-time safety. The SelectQuery automatically generates JOIN clauses based on the entity metadata. The type of join is determined by the @JoinColumn annotation:

  • INNER JOIN: Used when the attribute is marked as mandatory (e.g., @JoinColumn(nullable = false)).
  • LEFT JOIN: Used by default or when the attribute is explicitly marked as nullable (e.g., @Nullable or @JoinColumn(nullable = true)).

Data filtering can be defined using the where() method, which accepts a Criterion object. This object can represent a complex logical structure in the form of a binary tree, providing a clear and type-safe way to build nested conditions.

Automatically generated Meta* classes enable safe column mapping without the use of typo-prone text strings. The use of a SELECT statement can then look like this, for example:

Java
 
final EntityContext CTX = EntityContext.ofDefault();
final EntityManager<Employee, Long> EMPLOYEE_EM = CTX.entityManager(Employee.class);

List<Employee> select() {
    return SelectQuery.run(connection(), EMPLOYEE_EM, query -> query
            .sql("SELECT")                              // Optional: "SELECT" is the default
            .columnsOfDomain(true)
            .column(MetaEmployee.city, MetaCity.name)   // INNER JOIN (nullable = false)
            .column(MetaEmployee.city, MetaCity.countryCode)  
            .column(MetaEmployee.boss, MetaEmployee.name) // LEFT JOIN (nullable = true)
            .where(MetaEmployee.id.whereGe(1L))
            .tail("ORDER BY", MetaEmployee.id)
            .toList()
    );
}


If you need full control over building the SQL SELECT statement, use the SqlQuery class. This class provides an API with methods for type-safe insertion of database columns or just their labels. The individual approaches differ only in the way the SQL query is constructed. Regardless of the chosen approach, the database columns are ultimately mapped to entities using column aliases in the format: "city.name". The resulting ResultSet is also mapped to entities using this same mechanism via the ResultSetMapper class.

The EntityManager is used for working with entities, providing simple CRUD operations — including batch commands — through a Crud object. An interesting feature is the possibility of partial updates — the developer can specify an enumeration of columns to be updated, or pass the original object to the library, from which it will infer the changes itself. The mentioned classes are illustrated in a simplified class diagram. All listed methods are public:

Listed methods


Performance

Ujorm3 achieves great results in benchmark tests, where it is compared with some popular ORM libraries. The mechanism of writing values to domain objects also contributes to the good score. Instead of the traditional approach using Java reflection, the library generates and compiles its own classes at runtime. Such an approach generally reduces memory requirements, minimizes overhead, and saves work for the Garbage Collector. The library has no dependencies on external libraries, and the compiled benchmark module (including the Ujorm3 library itself) is less than 3 MB, which is advantageous for microservices and embedded environments. However, it is good to keep in mind that in a production environment, in conjunction with slower databases, the differences in performance may partially blur.

Getting Started

To try the library in your Java 17+ project, simply add the dependency to your Maven configuration:

XML
 
<dependency>
    <groupId>org.ujorm</groupId>
    <artifactId>ujo-core</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.ujorm</groupId>
    <artifactId>ujorm-orm</artifactId>
    <version>3.0.0</version>
</dependency>


To automatically generate metamodel classes, add the optional APT configuration to the build element:

XML
 
<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
        <configuration>
            <annotationProcessorPaths>
                <path>
                    <groupId>org.ujorm</groupId>
                    <artifactId>ujorm-meta-processor</artifactId>
                    <version>3.0.0</version>
                </path>
            </annotationProcessorPaths>
        </configuration>
    </plugin>
</plugins>


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.

Useful Links

  • Project Homepage
  • PetStore Demo
  • Benchmark Tests
  • More Examples as a JUnit Test
Relational database Java (programming language) Framework

Published at DZone with permission of Pavel Ponec. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Next-Level Persistence in Jakarta EE: How We Got Here and Why It Matters
  • Distributed Rate Limiting in Java: A Deep Dive into Bucket4j + PostgreSQL
  • Graceful Shutdown: Spring Framework vs Golang Web Services
  • Build a REST API With Just 2 Classes in Java and Quarkus

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook