<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Pavel Ponec</title>
    <description>The latest articles on Forem by Pavel Ponec (@pponec).</description>
    <link>https://forem.com/pponec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3837101%2Fffef3f8f-fb34-4a7a-9a80-77e9c29670e6.jpg</url>
      <title>Forem: Pavel Ponec</title>
      <link>https://forem.com/pponec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pponec"/>
    <language>en</language>
    <item>
      <title>Native SQL in Java without JDBC boilerplate — meet Ujorm3</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Tue, 19 May 2026 10:38:40 +0000</pubDate>
      <link>https://forem.com/pponec/native-sql-in-java-without-jdbc-boilerplate-meet-ujorm3-3ab2</link>
      <guid>https://forem.com/pponec/native-sql-in-java-without-jdbc-boilerplate-meet-ujorm3-3ab2</guid>
      <description>&lt;p&gt;If you've ever written raw JDBC, you know what's coming. Open a connection, create a &lt;code&gt;PreparedStatement&lt;/code&gt;, set parameters &lt;strong&gt;by index&lt;/strong&gt; (hope you counted right), iterate a &lt;code&gt;ResultSet&lt;/code&gt;, close everything in a &lt;code&gt;finally&lt;/code&gt; block, declare &lt;code&gt;SQLException&lt;/code&gt; on every method signature… It's a lot of ceremony for "give me some rows."&lt;/p&gt;

&lt;p&gt;I've been experimenting with &lt;a href="https://github.com/pponec/ujorm#-ujorm3-library" rel="noopener noreferrer"&gt;Ujorm3&lt;/a&gt;, a new lightweight ORM library for Java 17+. Here's a realistic example — a JOIN query that maps results including a nested relation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                    SELECT e.id, e.name, c.name AS "city.name"
                    FROM employee e
                    JOIN city c ON c.id = e.city_id
                    WHERE e.id &amp;gt;= :minId
                    """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"minId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me walk through what makes this tick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fluent API
&lt;/h2&gt;

&lt;p&gt;The whole operation is one readable chain. No juggling &lt;code&gt;Statement&lt;/code&gt; objects, no passing things between methods — you declare the SQL, bind parameters, specify the mapper, and collect. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Named parameters instead of positional &lt;code&gt;?&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Classic JDBC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// hope you counted correctly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ujorm3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"minId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You reference parameters by name in the SQL (&lt;code&gt;:minId&lt;/code&gt;) and bind them by name. No counting, no off-by-one errors when you insert a new parameter in the middle of a query, and the SQL stays readable.&lt;/p&gt;




&lt;h2&gt;
  
  
  No checked exceptions
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SQLException&lt;/code&gt; is a checked exception, so vanilla JDBC forces you to handle or rethrow it everywhere — even when there's nothing useful to say. Ujorm3 wraps these internally, so your methods stay clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// JDBC — forced to declare or catch&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;SQLException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Ujorm3 — nothing to declare&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Smart object mapping — including relations
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ResultSetMapper&lt;/code&gt; is a &lt;strong&gt;thread-safe&lt;/strong&gt; class that prepares its mapping model on first use and reuses it across all subsequent calls. This significantly reduces overhead when processing a large number of queries.&lt;/p&gt;

&lt;p&gt;Mapping is inferred automatically by default. You can optionally annotate your domain classes with standard &lt;code&gt;jakarta.persistence&lt;/code&gt; annotations (&lt;code&gt;@Table&lt;/code&gt;, &lt;code&gt;@Column&lt;/code&gt;, &lt;code&gt;@Id&lt;/code&gt;) for explicit control, but they're not required.&lt;/p&gt;

&lt;p&gt;The interesting bit is how it handles relations. The aliased column &lt;code&gt;"city.name"&lt;/code&gt; uses dot notation to map directly into a nested object — no extra configuration needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- maps to employee.getCity().getName() automatically&lt;/span&gt;
&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"city.name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library supports M:1 relations. 1:M collections are intentionally left out — a deliberate design choice to avoid hidden queries and N+1 problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Want compile-time safety? There's a metamodel for that.
&lt;/h3&gt;

&lt;p&gt;The string-based alias approach works great for getting started, but if you want the compiler to catch typos in column mappings, the optional APT plugin generates &lt;code&gt;Meta*&lt;/code&gt; classes from your domain objects. The query then looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
                    SELECT e.id  AS ${e.id}
                    , e.name     AS ${e.name}
                    , c.name     AS ${c.name}
                    FROM employee e
                    JOIN city c ON c.id = e.city_id
                    WHERE e.id &amp;gt;= :id
                    """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;   &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;${placeholder}&lt;/code&gt; syntax in the SQL template and the &lt;code&gt;label()&lt;/code&gt; method work together — the metamodel keys are type-parameterized descriptors that resolve column labels at runtime and carry full type information.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automatic resource management
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SqlQuery.run(...)&lt;/code&gt; handles closing the underlying &lt;code&gt;PreparedStatement&lt;/code&gt; and &lt;code&gt;ResultSet&lt;/code&gt; for you. No &lt;code&gt;try-with-resources&lt;/code&gt;, no resource leaks if mapping throws partway through.&lt;/p&gt;




&lt;h2&gt;
  
  
  There's more than just &lt;code&gt;SqlQuery&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The library offers three levels of abstraction — pick what fits your use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EntityManager&lt;/code&gt; — the fastest path for CRUD on a single table using a primary key;   generates the SQL itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SelectQuery&lt;/code&gt; — for fetching data including relations; supports type-safe &lt;code&gt;Criterion&lt;/code&gt;   filters composable with AND/OR operators; JOIN type (INNER vs LEFT) is inferred
automatically from the &lt;code&gt;nullable&lt;/code&gt; property of &lt;code&gt;@Column&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SqlQuery&lt;/code&gt; — low-level, full native SQL control; what we've been looking at above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;SelectQuery&lt;/code&gt; in action
&lt;/h2&gt;

&lt;p&gt;In many cases, the full &lt;code&gt;SELECT&lt;/code&gt; statement — columns, JOINs, and WHERE clause — can be generated automatically by &lt;code&gt;SelectQuery&lt;/code&gt; from the metamodel, so you don't have to write SQL at all. You still get the same object mapping under the hood.&lt;/p&gt;

&lt;p&gt;First, set up the shared context and entity manager (once, typically as static fields):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// EntityContext controls SQL logging; false = no param values in logs&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityContext&lt;/span&gt; &lt;span class="no"&gt;CTX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EntityContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSqlInfoWithParams&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_EM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CTX&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the query itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SelectQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_EM&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                            &lt;span class="c1"&gt;// select all columns, including foreign keys&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// add the city.name JOIN column&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;whereGe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;// WHERE id &amp;gt;= minId&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ORDER BY"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// append raw SQL fragment at the end&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.columns(true)&lt;/code&gt;&lt;/strong&gt; expands to all mapped columns of &lt;code&gt;Employee&lt;/code&gt;, including foreign key values (e.g. &lt;code&gt;city_id&lt;/code&gt;). The &lt;code&gt;true&lt;/code&gt; argument does not affect JOIN generation yet — that is driven by the next call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.column(MetaEmployee.city, MetaCity.name)&lt;/code&gt;&lt;/strong&gt; adds a specific column from a related entity. The library resolves which JOIN to emit based on the metamodel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.where(...)&lt;/code&gt;&lt;/strong&gt; takes a type-safe &lt;code&gt;Criterion&lt;/code&gt;. Conditions compose naturally with &lt;code&gt;.and()&lt;/code&gt; / &lt;code&gt;.or()&lt;/code&gt;, and because they're built from metamodel descriptors, a typo in an attribute name is a compile error, not a runtime surprise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.tail("ORDER BY", MetaEmployee.id)&lt;/code&gt;&lt;/strong&gt; appends a raw SQL fragment after the generated WHERE clause — a handy escape hatch for &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;LIMIT&lt;/code&gt;, window hints, or anything else the query builder doesn't cover.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result mapping works exactly the same way as in the &lt;code&gt;SqlQuery&lt;/code&gt; examples above — same &lt;code&gt;ResultSetMapper&lt;/code&gt; machinery, same dot-notation for nested objects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Instead of reflection, the library generates and compiles its own bytecode at runtime for reading and writing domain object fields — performance comparable to handwritten code. In benchmark comparisons against Hibernate, Jdbi, MyBatis, and others (running on PostgreSQL and H2) it performs very well. The entire compiled module including Ujorm3 itself is under 3 MB, which is nice for microservices.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this is NOT
&lt;/h2&gt;

&lt;p&gt;Not Hibernate. No entity scanning, no session factory, no proxy objects, no lazy&lt;br&gt;
loading surprises. You write SQL, you get objects back.&lt;/p&gt;

&lt;p&gt;Not jOOQ either — there's no Java DSL for building queries. You write plain SQL strings, which means you get full access to any database-specific syntax: window functions, CTEs, vendor extensions, whatever your DB supports.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Java 17+, final version 3.0.0 available on Maven Central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujo-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujorm-orm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optional APT plugin for metamodel generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;annotationProcessorPaths&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujorm-meta-processor&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/annotationProcessorPaths&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration tests cover PostgreSQL, MySQL, MariaDB, Oracle, and MS SQL Server (all via Docker).&lt;/p&gt;




&lt;h2&gt;
  
  
  When does this make sense?
&lt;/h2&gt;

&lt;p&gt;If you need JPA portability across databases or your company mandates a standard ORM, use Hibernate. If you want full SQL control, transparent behavior, and no hidden magic — and you'd rather not write raw JDBC — this hits a nice sweet spot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm#-ujorm3-library" rel="noopener noreferrer"&gt;Project homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm-petstore" rel="noopener noreferrer"&gt;PetStore demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/orm-benchmarks" rel="noopener noreferrer"&gt;Benchmark tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.javadoc.io/doc/org.ujorm/ujo-orm/latest/org/ujorm/orm/package-summary.html" rel="noopener noreferrer"&gt;JavaDoc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm/blob/ujorm3/project-m2/ujo-orm/src/test/java/org/ujorm/orm/tutorial/TutorialTest.java" rel="noopener noreferrer"&gt;More examples as JUnit tests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curious whether others are using similar lightweight wrappers, or if you've landed on&lt;br&gt;
a different approach for native SQL without going full ORM.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>orm</category>
    </item>
    <item>
      <title>Ujorm3 is out: a tiny, type-safe ORM for JavaBeans &amp; Records</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Mon, 18 May 2026 08:40:16 +0000</pubDate>
      <link>https://forem.com/pponec/ujorm3-new-lightweight-orm-for-javabeans-and-records-final-version-3l3k</link>
      <guid>https://forem.com/pponec/ujorm3-new-lightweight-orm-for-javabeans-and-records-final-version-3l3k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do the simplest thing that could possibly work.&lt;/em&gt; — Kent Beck, creator of Extreme Programming and pioneer of test-driven development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, I would like to introduce the final version of the Ujorm3 library with a newly written ORM module for working with JavaBean and Record objects in the context of relational databases. The goal of this module was a transparent solution without additional dependencies, supporting type-safe construction of SQL statements.&lt;/p&gt;

&lt;p&gt;For fast manipulation of domain object data, the library compiles its own bytecode at runtime, achieving performance comparable to handwritten code. The core of the library intensively utilizes the &lt;strong&gt;Typed Key Pattern&lt;/strong&gt; design pattern. Two interfaces in particular were carried over from the original version of the Ujorm library — &lt;code&gt;Ujo&lt;/code&gt; and &lt;code&gt;Key&lt;/code&gt;. Both interfaces applied this pattern back in 2007, even before the pattern received its name.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://matklad.github.io/2018/05/24/typed-key-pattern.html" rel="noopener noreferrer"&gt;Typed Key Pattern&lt;/a&gt; design pattern represents a technique for reading and writing domain object values through its key API, replacing traditional getters and setters with an internal property map. Keys are type-parameterized descriptors carrying information about the data type (or a default value) and, thanks to generics, ensure type safety without casting; they also allow mass operations on attributes without reflection. In terms of internal representation, this approach corresponds to the Typesafe Heterogeneous Container concept, which was first systematically published by Joshua Bloch in May 2008 in the second edition of the book &lt;strong&gt;Effective Java&lt;/strong&gt; following the introduction of generics to the Java language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let me remind you of some basic information from the previous blog: the default mapping of entity attributes to database columns is described using JPA annotations from the Jakarta package. The &lt;code&gt;@Column&lt;/code&gt; annotation is used for mapping attributes to columns, and &lt;code&gt;@Enumerated&lt;/code&gt; is used for mapping enum types. The table name is declared with the &lt;code&gt;@Table&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;Since the library does not use any binary-modified classes, partial column updates in an UPDATE statement require providing a list of modified attribute names (for example, as a &lt;code&gt;String[]&lt;/code&gt; array) or the original object, from which the library creates this list itself. A single database table can be serviced by several classes — typically representing a subset of the entity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyrs8uccbasly3369ay8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyrs8uccbasly3369ay8.png" height="300" width="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the goals of the new library was to find the optimal balance between utility value and minimalist source code. The author believes that such an approach has a positive impact on minimizing errors and significantly extends the lifespan of the library. However, certain limitations arise from this approach. For example: The library does not support lazy-loading and supports only M:1 relationships when mapping relations.&lt;/p&gt;

&lt;p&gt;The ORM module of the Ujorm3 library offers a &lt;strong&gt;set&lt;/strong&gt; of tools with different levels of abstraction for database commands. Choose the solution that best suits your specific requirement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;EntityManager&lt;/code&gt; — the fastest way to handle CRUD (Create, Read, Update, Delete) operations with a single table using a primary key. For such elementary cases, the class generates the SQL command itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SelectQuery&lt;/code&gt; — a tool primarily designed for fetching data, including relations. It supports filtering data using type-safe &lt;code&gt;Criterion&lt;/code&gt; objects, which can be arranged into a binary tree (using AND and OR operators). An alternative is using the &lt;code&gt;bind()&lt;/code&gt; method. For specific cases, the beginning and end of the generated SQL command can be manually appended.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SqlQuery&lt;/code&gt; — a low-level universal class for general requirements. It allows writing native SQL while maintaining safety using the &lt;code&gt;bind()&lt;/code&gt;, &lt;code&gt;label()&lt;/code&gt;, and &lt;code&gt;column()&lt;/code&gt; methods. Using a generic mapper, the library can automatically populate relational objects as well. The last two classes share a common ancestor, which can serve as a base for any future implementations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at simple usage examples, starting with the low-level approach. The following method inserts a city into the database table (using a &lt;em&gt;fluent API&lt;/em&gt;) and returns the identifier of the inserted row.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="nf"&gt;insertCity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
             INSERT INTO city
             ( name,  country_code) VALUES
             (:name, :countryCode )
             """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"countryCode"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeInsert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rs&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLong&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next example searches for employees and fetches their data, including the city name. The &lt;code&gt;ResultSetMapper&lt;/code&gt; class supports multithreaded access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
             &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findEmployees&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
             SELECT e.id  AS ${e.id}
             , e.name     AS ${e.name}
             , c.name     AS ${c.name}
             FROM employee e
             JOIN city c ON c.id = e.city_id
             WHERE e.id &amp;gt;= :id
             """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.id"&lt;/span&gt;  &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Key Path mapping&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// Generic mapping including relations&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MetaEmployee&lt;/code&gt; class is an automatically generated metamodel created using the optional &lt;code&gt;ujorm-meta-processor&lt;/code&gt; APT plugin. However, mapping can also be written directly in the SQL SELECT statement at the column alias position in the format of dot-separated property names (properties, for example, &lt;code&gt;boss.name&lt;/code&gt;). However, it needs to be enclosed in quotes (or other characters depending on the database type). If the DB column name matches the attribute, explicit mapping is unnecessary. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;"city.name"&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last example performs a similar &lt;code&gt;SELECT&lt;/code&gt; using the &lt;code&gt;SelectQuery&lt;/code&gt; class. Here, the &lt;code&gt;columns(true)&lt;/code&gt; method loads all columns of the table, including foreign keys. The &lt;code&gt;column(...)&lt;/code&gt; method adds another column to the mapping and can also handle multiple relations.&lt;/p&gt;

&lt;p&gt;The relation type is determined by the &lt;code&gt;nullable&lt;/code&gt; property from the JPA annotation: a mandatory object attribute (marked as &lt;code&gt;nullable=false&lt;/code&gt;) generates an &lt;code&gt;INNER JOIN&lt;/code&gt; relation type, otherwise it generates a &lt;code&gt;LEFT JOIN&lt;/code&gt;. The beginning of the SQL statement can (optionally) be changed using the &lt;code&gt;sql()&lt;/code&gt; method, and the end can (optionally) be appended using the &lt;code&gt;tail()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityContext&lt;/span&gt; &lt;span class="no"&gt;CTX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EntityContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofDefault&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_EM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;CTX&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SelectQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_EM&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Optional&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// INNER JOIN&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boss&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// LEFT JOIN&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;whereGe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;whereGe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ORDER BY"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code of the Ujorm3 project is thoroughly covered by JUnit tests. Integration tests are run via a Bash script (in the &lt;code&gt;bin&lt;/code&gt; directory) against dockerized databases: PostgreSQL, MySQL, MariaDB, OracleFree, and MSSQL Server. More detailed information about the Ujorm3 library can be found on the &lt;a href="https://github.com/pponec/ujorm#-ujorm3-library" rel="noopener noreferrer"&gt;homepage&lt;/a&gt; of the project — including additional examples.&lt;/p&gt;

&lt;p&gt;A reference usage of &lt;strong&gt;Ujorm3&lt;/strong&gt; can be found in the &lt;a href="https://github.com/pponec/ujorm-petstore#ujorm-petstore" rel="noopener noreferrer"&gt;PetStore&lt;/a&gt; project,  which uses the lightweight Avaje library for dependency injection. The PetStore project then gave rise to a prototype web application called &lt;a href="https://topmovies.ujorm.org/" rel="noopener noreferrer"&gt;TopMovies&lt;/a&gt; for movie viewers — this application recommends movies to users based on how their ratings match a virtual group with similar preferences.&lt;/p&gt;

&lt;p&gt;Apart from the Ujorm library, this project also has no other dependencies. Data is stored in an H2 database, or optionally in PostgreSQL. Although the project is not publicly available, you can freely try the prototype at the provided link (the number of fictitious users is limited).&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm#-ujorm3-library" rel="noopener noreferrer"&gt;Homepage&lt;/a&gt; — is part of the Ujorm3 project hosted on GitHub.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm-petstore#ujorm-petstore" rel="noopener noreferrer"&gt;PetStore&lt;/a&gt; — a sample open-source application demonstrating the Ujorm3 library. It uses the ORM module for database queries and the Element class to build the UI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://topmovies.ujorm.org/" rel="noopener noreferrer"&gt;TopMovies&lt;/a&gt; — a prototype application for recommending movies based on the alignment of user ratings with a virtual movie group.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.javadoc.io/doc/org.ujorm/ujo-orm/latest/org/ujorm/orm/package-summary.html" rel="noopener noreferrer"&gt;JavaDoc&lt;/a&gt; — class description.&lt;/li&gt;
&lt;li&gt;An ORM performance &lt;a href="https://github.com/pponec/orm-benchmarks?tab=readme-ov-file#orm-benchmark" rel="noopener noreferrer"&gt;comparison&lt;/a&gt; — comparing performance and other metrics with Hibernate, Jdbi, MyBatis, and other libraries. Tests run on PostgreSQL (in Docker) and H2 (in-memory) databases using Java 25.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>database</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>ORM library Ujorm 3.0 is released</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Fri, 15 May 2026 10:40:12 +0000</pubDate>
      <link>https://forem.com/pponec/orm-library-ujorm-30-is-released-2lce</link>
      <guid>https://forem.com/pponec/orm-library-ujorm-30-is-released-2lce</guid>
      <description>&lt;p&gt;Today, the final version of Ujorm 3.0.0 was released, featuring a completely new ORM module for working with JavaBean and Record objects. The goal was a transparent solution with no additional dependencies, supporting type-safe construction of SQL statements. &lt;/p&gt;

&lt;p&gt;Ujorm3 requires Java 17 or higher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujo-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujo-orm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More information is available on the project's homepage on &lt;a href="https://github.com/pponec/ujorm/#-ujorm3-library" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc4oyf62ey9vigibmk11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc4oyf62ey9vigibmk11.png" alt=" " width="800" height="798"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>java</category>
      <category>news</category>
      <category>sql</category>
    </item>
    <item>
      <title>Stop Debating ORM vs JDBC — Measure These 5 Things First (Java Guide)</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Sat, 25 Apr 2026 05:47:16 +0000</pubDate>
      <link>https://forem.com/pponec/stop-debating-orm-vs-jdbc-measure-these-5-things-first-java-guide-1pcn</link>
      <guid>https://forem.com/pponec/stop-debating-orm-vs-jdbc-measure-these-5-things-first-java-guide-1pcn</guid>
      <description>&lt;p&gt;Most "ORM vs JDBC" discussions are opinion wars.&lt;/p&gt;

&lt;p&gt;After building and benchmarking data-access layers in Java, my takeaway is simple:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;you should not choose by framework brand, but by measured query behavior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this post, I’ll share a practical checklist you can apply to any stack (Hibernate, MyBatis, JDBI, jOOQ, custom JDBC, or lightweight ORM tools).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why teams get stuck in the same loop
&lt;/h2&gt;

&lt;p&gt;You’ve probably seen this pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ORM starts great for CRUD velocity&lt;/li&gt;
&lt;li&gt;project grows&lt;/li&gt;
&lt;li&gt;read paths get slower (N+1, over-fetching, accidental joins)&lt;/li&gt;
&lt;li&gt;team starts rewriting "hot spots" in SQL&lt;/li&gt;
&lt;li&gt;architecture becomes mixed anyway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of asking &lt;em&gt;"Which library wins?"&lt;/em&gt;, ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"For this endpoint and this data shape, what is the cheapest and most predictable query path?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The 5 metrics that actually matter
&lt;/h2&gt;

&lt;p&gt;If you only track one metric (e.g., average response time), you will make bad decisions.&lt;/p&gt;

&lt;p&gt;Measure these together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;p95 / p99 latency&lt;/strong&gt; per endpoint
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query count&lt;/strong&gt; per request (detect N+1 fast)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows + columns fetched&lt;/strong&gt; (projection discipline)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocation rate / memory pressure&lt;/strong&gt; (mapping overhead is real)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL transparency&lt;/strong&gt; (how easy it is to inspect generated SQL)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When these 5 are stable, your persistence layer is usually healthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical architecture that works in real projects
&lt;/h2&gt;

&lt;p&gt;I’ve had the best results with a hybrid approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ORM path&lt;/strong&gt; for routine transactional CRUD and lifecycle-heavy writes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL-first path&lt;/strong&gt; for read-heavy endpoints, reporting, and complex joins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated guardrails&lt;/strong&gt; in CI (query count assertions, slow-query budget)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This removes ideology from the discussion and keeps both productivity and control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common anti-patterns (and cheap fixes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) "SELECT * everywhere"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Return only fields you need (DTO/projection), not whole entity graphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Hidden join explosions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Track query count per endpoint in tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Treating average latency as "good enough"
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Watch p95/p99, not just average.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Framework-only tuning
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Start with query shape + indexes first, framework tuning second.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I changed in my own Java stack
&lt;/h2&gt;

&lt;p&gt;In my recent work, I focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit SQL visibility&lt;/li&gt;
&lt;li&gt;type-safe mapping without stringly-typed glue&lt;/li&gt;
&lt;li&gt;minimizing runtime "magic" for hot paths&lt;/li&gt;
&lt;li&gt;reproducible benchmarks (code + methodology visible)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That led me to keep exploring lightweight, transparent ORM design in &lt;strong&gt;Ujorm3&lt;/strong&gt; and a small sample app (&lt;strong&gt;PetStore&lt;/strong&gt;) that demonstrates the approach.&lt;/p&gt;

&lt;p&gt;If you want context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/pponec/ujorm3-a-new-lightweight-orm-for-javabeans-and-records-3pph"&gt;Ujorm3: A New Lightweight ORM for JavaBeans and Records&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/pponec/petstore-pure-java-for-ui-and-safe-sql-mapping-hh9"&gt;PetStore: Pure Java for UI and Safe SQL Mapping&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>java</category>
      <category>performance</category>
    </item>
    <item>
      <title>PetStore: Pure Java for UI and Safe SQL Mapping</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Thu, 02 Apr 2026 07:41:35 +0000</pubDate>
      <link>https://forem.com/pponec/petstore-pure-java-for-ui-and-safe-sql-mapping-hh9</link>
      <guid>https://forem.com/pponec/petstore-pure-java-for-ui-and-safe-sql-mapping-hh9</guid>
      <description>&lt;p&gt;I would like to introduce the &lt;strong&gt;PetStore&lt;/strong&gt; sample application, which demonstrates a pure "Java-first" approach to web interface development. The entire project is built on the idea of maximum type safety and clarity, achieved through two modules of the Ujorm3 library. These effectively eliminate common abstraction layers that often complicate development and debugging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy73qzcl760lnp9dijr71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy73qzcl760lnp9dijr71.png" alt="Screenshot" width="711" height="606"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Two Pillars of Ujorm3
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. UI Creation without Templates (ujo-web)
&lt;/h3&gt;

&lt;p&gt;We have replaced traditional engines like Thymeleaf or JSP with pure Java code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type-safe rendering:&lt;/strong&gt; HTML is generated using the &lt;code&gt;HtmlElement&lt;/code&gt; builder and &lt;code&gt;try-with-resources&lt;/code&gt; blocks.
This approach allows writing Java code in a natural tree structure that faithfully mirrors the HTML structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring with full IDE support:&lt;/strong&gt; Since the UI is defined in Java, everything you are used to works – autocomplete (IntelliSense), instant refactoring (e.g., extracting a table into a &lt;code&gt;renderTable()&lt;/code&gt; method), and correctness checking while writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No more parameter errors:&lt;/strong&gt; The &lt;code&gt;HttpParameter&lt;/code&gt; interface uses enums to define web parameters.
This practically eliminates typos in form field names, which in standard solutions only manifest at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Modern Database Handling (ujo-orm)
&lt;/h3&gt;

&lt;p&gt;Forget about complex XML mapping or runtime errors in SQL queries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Using Java Records:&lt;/strong&gt; Standard Java records serve as domain objects (&lt;code&gt;Pet&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;).
They are naturally immutable, clean, and fully compatible with &lt;code&gt;@Table&lt;/code&gt; and &lt;code&gt;@Column&lt;/code&gt; annotations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-Safe SQL Builder:&lt;/strong&gt; An annotation processor generates metamodels (e.g., &lt;code&gt;MetaPet&lt;/code&gt;) during compilation.
The compiler catches an error in a column name, not an application crash in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL under control:&lt;/strong&gt; No unexpected &lt;code&gt;LazyInitializationException&lt;/code&gt; or hidden N+1 problems.
You have absolute control over every &lt;code&gt;SqlQuery&lt;/code&gt;.
Moreover, you can easily map results from native SQL back to records using the &lt;code&gt;label()&lt;/code&gt; method.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📁 Code Sample (PetServlet)
&lt;/h2&gt;

&lt;p&gt;The project is designed with an emphasis on straightforwardness.&lt;br&gt;
The following example from a stateless servlet demonstrates how elegantly logic, parameters, and HTML generation can be connected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doGet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofServlet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextPath&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ACTION&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Action:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;paramValueOf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;petId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PET_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Long:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPets&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCategories&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;petToEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EDIT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPetById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;petId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlElement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BOOTSTRAP_CSS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Css&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Css&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mt5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;renderHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextPath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;renderTable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;renderForm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;petToEdit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what a native SQL query looks like in pure Java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;PET_EM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
             &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
        SELECT p.id AS ${p.id}
        , p.name    AS ${p.name}
        , p.status  AS ${p.status}
        , c.id      AS ${c.id}
        , c.name    AS ${c.name}
        FROM pet p
        LEFT JOIN category c ON c.id = p.category_id
        WHERE p.id &amp;gt;= :id
        ORDER BY p.id
        """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.status"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;streamMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PET_EM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💡 Why Choose This Approach?
&lt;/h2&gt;

&lt;p&gt;This architecture represents an interesting alternative for developers who are tired of heavy JPA frameworks or bloated frontend technologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Ujorm PetStore shines most:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;B2B and administrative applications:&lt;/strong&gt; Where development speed and long-term maintainability are important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservices:&lt;/strong&gt; Thanks to minimal overhead and fast startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projects with HTMX:&lt;/strong&gt; It perfectly complements modern trends of returning to server-side rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;"Java-First"&lt;/strong&gt; philosophy drastically reduces context switching between Java, SQL, XML, and various templating languages.&lt;br&gt;
Everything you need is under the protection of the compiler.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Try It Locally
&lt;/h2&gt;

&lt;p&gt;The application utilizes the best of the current ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java 25&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot 3.5.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;H2 Database&lt;/strong&gt; (In-memory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All you need is JDK 25 and Maven installed, then just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application will start at &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources and Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm-petstore/blob/main/src/main/java/org/ujorm/petstore/PetServlet.java" rel="noopener noreferrer"&gt;PetServlet.java&lt;/a&gt; – A stateless Servlet acting as both Controller and View. It handles HTTP communication and builds the HTML.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm-petstore/blob/main/src/main/java/org/ujorm/petstore/Dao.java" rel="noopener noreferrer"&gt;Dao.java&lt;/a&gt; – Data access layer integrating Spring JDBC with Ujorm &lt;code&gt;EntityManager&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm/tree/ujorm3" rel="noopener noreferrer"&gt;Ujorm 3 Library on GitHub&lt;/a&gt; – Official library repository.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/orm-benchmarks" rel="noopener noreferrer"&gt;ORM Benchmarks&lt;/a&gt; – How this approach compares to the competition.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Does it make sense to you to have the UI and DB layers so tightly coupled with the compiler? I will be glad for any technical feedback!&lt;/p&gt;

</description>
      <category>java</category>
      <category>showdev</category>
      <category>sql</category>
      <category>ui</category>
    </item>
    <item>
      <title>Ujorm3: A New Lightweight ORM for JavaBeans and Records</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:27:15 +0000</pubDate>
      <link>https://forem.com/pponec/ujorm3-a-new-lightweight-orm-for-javabeans-and-records-3pph</link>
      <guid>https://forem.com/pponec/ujorm3-a-new-lightweight-orm-for-javabeans-and-records-3pph</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"Do the simplest thing that could possibly work."&lt;br&gt;
— Kent Beck, creator of Extreme Programming and pioneer of Test-Driven Development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyrs8uccbasly3369ay8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyrs8uccbasly3369ay8.png" height="300" width="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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, &lt;strong&gt;Ujorm3&lt;/strong&gt;, which I believe beats its competitors with 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 &lt;strong&gt;first&lt;/strong&gt; release candidate (RC1) is now available in the Maven Central Repository, released under the free Apache License 2.0.&lt;/p&gt;

&lt;p&gt;The library builds on the familiar principles of JDBC but adds a thin layer of a user-friendly API on top of them. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application API Classes
&lt;/h2&gt;

&lt;p&gt;The core class for database operations is &lt;code&gt;SqlQuery&lt;/code&gt; (originally named &lt;a href="https://blog.root.cz/ponec/170-radku-java-kodu-pro-sql/" rel="noopener noreferrer"&gt;SqlParamBuilder&lt;/a&gt;), which acts as a facade over &lt;code&gt;PreparedStatement&lt;/code&gt;. The object supports named parameters for SQL statements, eliminates checked exceptions, and provides the result of a &lt;code&gt;SELECT&lt;/code&gt; operation as an efficient &lt;code&gt;Stream&amp;lt;ResultSet&amp;gt;&lt;/code&gt;. The mapping of data from a &lt;code&gt;ResultSet&lt;/code&gt; to domain objects is then handled by a separate class called &lt;code&gt;ResultSetMapper&amp;lt;DOMAIN&amp;gt;&lt;/code&gt;. Its instance prepares the mapping model upon first use and subsequently reuses it, which significantly reduces the overhead when processing a large volume of queries.&lt;/p&gt;

&lt;p&gt;Mapping class attributes to database columns can be specified using annotations from the &lt;code&gt;jakarta.persistence&lt;/code&gt; package (&lt;code&gt;@Table&lt;/code&gt;, &lt;code&gt;@Column&lt;/code&gt;, &lt;code&gt;@Id&lt;/code&gt;), but the library can infer some properties even without them. Both mutable JavaBeans and immutable Records are fully supported. Ujorm3 only works with M:1 relations—1:M collections are intentionally omitted to prevent the generation of hidden queries and N+1 problems. Relational columns of a &lt;code&gt;SELECT&lt;/code&gt; statement can be mapped using their labels in the format &lt;code&gt;"city.name"&lt;/code&gt;, which contain a dot-chained list of Java attributes of the domain objects. However, it is better to use a type-safe metamodel.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            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 &amp;gt; :employeeId
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.country_code"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"b.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boss&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"employeeId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;streamMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that the domain class does not need to be registered anywhere in advance. For efficient work, however, I recommend creating a static mapper, whose implementation is prepared for multithreaded access. The &lt;code&gt;column()&lt;/code&gt; method adds a database column with a label to the SQL template at the position of the &lt;code&gt;${COLUMNS}&lt;/code&gt; placeholder. An alternative &lt;code&gt;label()&lt;/code&gt; method is also supported, allowing you to explicitly declare only column labels, thereby keeping the SQL query in the Java code closer to its native notation. However, these two approaches cannot be combined in a single query.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EntityManager&lt;/code&gt; is used for working with entities, providing simple CRUD operations—including batch commands—through a &lt;code&gt;Crud&lt;/code&gt; 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:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19wyj19wahv9ykdegsx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19wyj19wahv9ykdegsx1.png" alt="Class diagram" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Ujorm3 achieves very good &lt;a href="https://github.com/pponec/orm-benchmarks/tree/development?tab=readme-ov-file#orm-benchmark" rel="noopener noreferrer"&gt;results&lt;/a&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To try the library in your Java 17+ project, simply add the dependency to your Maven configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujo-core&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0-RC1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujorm-orm&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0-RC1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To automatically generate metamodel classes, add the optional APT configuration to the &lt;code&gt;build&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-compiler-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.14.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;annotationProcessorPaths&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;ujorm-meta-processor&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.0-RC1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/annotationProcessorPaths&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose the Ujorm3 Library?
&lt;/h2&gt;

&lt;p&gt;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 Ujorm3 library is definitely worth a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm/tree/ujorm3?tab=readme-ov-file#-ujorm3-library" rel="noopener noreferrer"&gt;Project Homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm-petstore?tab=readme-ov-file#ujorm-petstore" rel="noopener noreferrer"&gt;PetStore Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/orm-benchmarks/tree/development?tab=readme-ov-file#orm-benchmark" rel="noopener noreferrer"&gt;Benchmark Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm/blob/ujorm3/project-m2/ujo-orm/src/test/java/org/ujorm/orm/tutorial/TutorialTest.java" rel="noopener noreferrer"&gt;More Examples as a JUnit Test&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>java</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
