<?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>DEV Community: Z. Gao</title>
    <description>The latest articles on DEV Community by Z. Gao (@sqlfirst_dev).</description>
    <link>https://dev.to/sqlfirst_dev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3990021%2F7b699d20-1afb-4fdd-a3cf-8eda7730745f.png</url>
      <title>DEV Community: Z. Gao</title>
      <link>https://dev.to/sqlfirst_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sqlfirst_dev"/>
    <language>en</language>
    <item>
      <title>SQL-First Paradigm: Rethinking Persistence Layer Design from First Principles</title>
      <dc:creator>Z. Gao</dc:creator>
      <pubDate>Sat, 20 Jun 2026 02:29:39 +0000</pubDate>
      <link>https://dev.to/sqlfirst_dev/sql-first-paradigm-rethinking-persistence-layer-design-from-first-principles-4kne</link>
      <guid>https://dev.to/sqlfirst_dev/sql-first-paradigm-rethinking-persistence-layer-design-from-first-principles-4kne</guid>
      <description>&lt;h2&gt;
  
  
  Foreword
&lt;/h2&gt;

&lt;p&gt;In the Java backend world, ORM (JPA/Hibernate) and MyBatis have long been the default choices for data persistence. But after years of real‑world development, many engineers eventually run into a recurring tension:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;There is a fundamental semantic gap between general‑purpose programming languages (3GL) and SQL (4GL).&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Attempting to force‑fit SQL into object‑oriented abstractions — or burying SQL under layers of XML tags — often leads to redundant code, limited expressiveness, and painful debugging.&lt;/p&gt;

&lt;p&gt;This article explores the &lt;strong&gt;SQL‑First persistence paradigm&lt;/strong&gt; from first principles. We’ll examine the 3GL vs. 4GL distinction, the inherent trade‑offs in ORM and MyBatis, and how a lightweight approach — using &lt;code&gt;simple-dao&lt;/code&gt; as a concrete example — can streamline complex queries while keeping SQL fully transparent.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repositories&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Core Framework: &lt;a href="https://github.com/gzz2017gzz/simple-dao" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;System Base Starter: &lt;a href="https://github.com/gzz2017gzz/simple-dao-starter" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code Generator: &lt;a href="https://github.com/gzz2017gzz/simple-dao-coder" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-coder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Production Demo Project: &lt;a href="https://github.com/gzz2017gzz/simple-dao-demo" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Root Cause: 3GL vs. 4GL — Two Different Universes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Two Worlds, Two Mathematical Foundations
&lt;/h3&gt;

&lt;p&gt;Modern software development deals with two fundamentally different language families:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;3GL (General‑Purpose Languages)&lt;/th&gt;
&lt;th&gt;4GL (SQL)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Representatives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java, C#, Go, Python, C++&lt;/td&gt;
&lt;td&gt;SQL (relational database language)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mathematical Basis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Graph Theory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Set Theory + Predicate Logic&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Paradigm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Imperative / Object‑Oriented&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core Elements&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Objects, references, methods, inheritance, polymorphism&lt;/td&gt;
&lt;td&gt;Tables, rows, columns, keys, relationships&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mental Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tells the computer &lt;strong&gt;“how”&lt;/strong&gt; to do something&lt;/td&gt;
&lt;td&gt;Tells the database &lt;strong&gt;“what”&lt;/strong&gt; to retrieve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Abstraction Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Algorithms + data structures in memory&lt;/td&gt;
&lt;td&gt;Set operations on disk‑resident structured data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1.2 The Semantic Gap Is Inherent
&lt;/h3&gt;

&lt;p&gt;There is &lt;strong&gt;no perfect one‑to‑one mapping&lt;/strong&gt; between 3GL object models and 4GL relational models — this is not a framework limitation, but a mathematical reality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object inheritance and polymorphism have no direct relational counterpart.&lt;/li&gt;
&lt;li&gt;Navigating object references (e.g., &lt;code&gt;user.getOrderList()&lt;/code&gt;) is not the same as a database JOIN — performance models are completely different.&lt;/li&gt;
&lt;li&gt;In‑memory object identity (reference address) ≠ database primary key constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Any framework that tries to fully bridge these worlds will inevitably introduce abstraction leaks, performance surprises, and steep learning curves.&lt;/strong&gt; The question is not &lt;em&gt;whether&lt;/em&gt; the gap exists, but &lt;em&gt;how&lt;/em&gt; we choose to manage it.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why SQL and Relational Databases Are Still Relevant
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 Relational Databases Remain the Backbone of Enterprise Data
&lt;/h3&gt;

&lt;p&gt;Many modern database types — key‑value stores, time‑series databases, vector databases, graph databases — are essentially &lt;strong&gt;specialised variations&lt;/strong&gt; of the relational model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KV stores are single‑table relational tables with just key and value.&lt;/li&gt;
&lt;li&gt;Time‑series and vector databases add specific indexes and storage optimisations on top of relational structures.&lt;/li&gt;
&lt;li&gt;Graph databases can be fully represented using node and edge tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For core business domains (users, orders, permissions, campus management, ERP), the relational database remains the primary source of truth. Middleware like Redis, Elasticsearch, and ClickHouse serve as supplementary layers — not replacements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 SQL: A Universal, Cross‑Language Data Language
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strong standardisation&lt;/strong&gt;: Core SQL syntax has been stable for decades, with only minor dialect variations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full expressiveness&lt;/strong&gt;: From simple CRUD to complex joins, subqueries, grouping, aggregation, and window functions — SQL handles it all natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Universal compatibility&lt;/strong&gt;: Every mainstream language provides a standard SQL interface — Java/Spring JDBC, Python/DB‑API, C#/Dapper, Go/sqlx, Rust/sqlx, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQL isn’t going away, and neither are relational databases. A persistence design that embraces SQL natively will remain relevant for the foreseeable future.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Mainstream Frameworks: A Balanced Look at Their Trade‑offs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 ORM (JPA/Hibernate): A Noble but Imperfect Abstraction
&lt;/h3&gt;

&lt;p&gt;The ORM promise — &lt;em&gt;map relational data to object models&lt;/em&gt; — sounds elegant. But given the 3GL/4GL gap, it inevitably introduces challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semantic distortion&lt;/strong&gt;: Database set operations get translated into object navigation, forcing developers to mentally switch between OOP and relational thinking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL opacity&lt;/strong&gt;: Auto‑generated SQL can be inefficient, hard to tune, and unpredictable under complex joins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;N+1 query problem&lt;/strong&gt;: Object traversal frequently triggers multiple database round‑trips, becoming a performance bottleneck in high‑traffic scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double learning curve&lt;/strong&gt;: Developers must master not only Java/OOP but also HQL/JPQL, caching strategies, lazy loading pitfalls, and framework‑specific “gotchas.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ORM works well for very simple CRUD, but becomes a liability as soon as you need complex joins, aggregations, or reports.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 MyBatis: A Mapping Tool That Gained Too Much Baggage
&lt;/h3&gt;

&lt;p&gt;MyBatis is often called a “semi‑ORM,” but its core job is straightforward: &lt;strong&gt;parameter binding + result set mapping&lt;/strong&gt;. It does not generate SQL or manage object relationships.&lt;/p&gt;

&lt;p&gt;To achieve this, however, it has accumulated significant complexity over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dual‑file overhead&lt;/strong&gt;: Mapper interfaces + XML/annotations, leading to fragmented code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic tag hell&lt;/strong&gt;: &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;where&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;foreach&amp;gt;&lt;/code&gt;, OGNL expressions turn dynamic SQL into a puzzle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex interceptor system&lt;/strong&gt;: Custom extensions are hard to write and maintain; most teams rely on community plugins, which can break across upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare this with Spring JDBC, where a single &lt;code&gt;RowMapper&lt;/code&gt; does the mapping job in one line. &lt;strong&gt;Much of MyBatis’s additional surface area is overhead, not necessity.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Did MyBatis Become So Popular?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Historical momentum&lt;/strong&gt;: Early Hibernate was over‑opinionated and restricted native SQL access; MyBatis offered a middle ground.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bandwagon effect&lt;/strong&gt;: Widespread adoption made it the “safe” choice in many organisations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misplaced separation myth&lt;/strong&gt;: The idea that “SQL should be separated from code” became dogma, even though it often just adds indirection.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. The SQL‑First Paradigm: Design Principles and Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Core Insight: Dynamic SQL Has a Gradient
&lt;/h3&gt;

&lt;p&gt;A standard SQL statement can be broken into clauses — and &lt;strong&gt;not all clauses change at the same frequency&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clause&lt;/th&gt;
&lt;th&gt;Dynamism&lt;/th&gt;
&lt;th&gt;Typical Behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SELECT / FROM / JOIN / GROUP BY&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Table structures and relationships are stable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HAVING / ORDER BY&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Occasionally adjusted for new reporting needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WHERE / LIMIT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Business filters and pagination change constantly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This observation drives the SQL‑First strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stable parts&lt;/strong&gt; (SELECT, FROM, JOIN, GROUP BY) → &lt;strong&gt;Hand‑written SQL&lt;/strong&gt;, with no framework‑imposed abstraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High‑dynamism parts&lt;/strong&gt; (WHERE, pagination, sorting) → &lt;strong&gt;Encapsulated in a 3GL (Java) condition layer&lt;/strong&gt;, enabling reusability, type safety, and easy debugging.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The goal is not to translate between 3GL and 4GL, but to let each excel in its domain: SQL stays SQL, Java stays Java, with a thin bridge between them.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Two Golden Rules of SQL‑First Design
&lt;/h3&gt;

&lt;p&gt;These are non‑negotiable boundaries that distinguish SQL‑First from ORM/MyBatis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No wrapping of SQL keywords or native syntax&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The framework does not provide its own DSL like &lt;code&gt;eq()&lt;/code&gt; or &lt;code&gt;like()&lt;/code&gt;. Developers write standard SQL fragments directly (&lt;code&gt;AND field = ?&lt;/code&gt;).&lt;br&gt;&lt;br&gt;
→ Zero learning curve, full compatibility with all database‑specific features, and zero migration cost if you switch frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No shielding of database capabilities or native exceptions&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Stored procedures, DDL, streaming queries, vendor‑specific functions all work out‑of‑the‑box. SQL exceptions are propagated directly, not wrapped in framework‑specific errors.&lt;br&gt;&lt;br&gt;
→ Full power of the database, and intuitive debugging.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4.3 A Three‑Layer Architecture (Implemented in &lt;code&gt;simple-dao&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The SQL‑First paradigm naturally decomposes into three distinct layers, each with a clear responsibility:&lt;/p&gt;

&lt;h4&gt;
  
  
  Layer 1: Condition Layer (&lt;code&gt;BaseCondition&lt;/code&gt;) — for High‑Dynamism Clauses
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Centralised management of &lt;code&gt;WHERE&lt;/code&gt; conditions, pagination, and sorting. This is the heart of the paradigm.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides semantic methods: &lt;code&gt;and()&lt;/code&gt;, &lt;code&gt;in()&lt;/code&gt;, &lt;code&gt;like()&lt;/code&gt;, conditional &lt;code&gt;add()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Automatically collects parameter values for SQL placeholders — no manual parameter list maintenance.&lt;/li&gt;
&lt;li&gt;All conditions converge in a single &lt;code&gt;addCondition()&lt;/code&gt; method, ensuring consistent coding standards across the team.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Value&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eliminates the single‑table vs. join‑table mental split&lt;/strong&gt;: Whether it’s a simple CRUD or a 15‑table report, the same condition API is used.&lt;/li&gt;
&lt;li&gt;Adding or removing a filter is a one‑line change.&lt;/li&gt;
&lt;li&gt;Supports condition splitting and parameter merging (&lt;code&gt;mergeParams&lt;/code&gt;) for complex, modularised business queries.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Layer 2: Execution Layer (&lt;code&gt;BaseSql&lt;/code&gt;) — the Bridge
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: A pure execution channel that takes &lt;strong&gt;static SQL + dynamic conditions + parameters&lt;/strong&gt; and executes them via Spring JDBC.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does &lt;strong&gt;not parse or modify SQL&lt;/strong&gt; — fully respects the two golden rules.&lt;/li&gt;
&lt;li&gt;Provides cross‑dialect pagination, SQL logging with parameter substitution, and other convenience utilities.&lt;/li&gt;
&lt;li&gt;Fully leverages Spring’s transaction and connection pool management — no re‑invention.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Layer 3: Single‑Table Helper Layer (&lt;code&gt;BaseDao&lt;/code&gt;) — for Stable Scenarios
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: Automates single‑table CRUD by resolving entity metadata via annotations at startup (one‑time reflection).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capabilities&lt;/strong&gt;: Automatic primary key generation, audit field population (creator/creation time), and global logical delete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation&lt;/strong&gt;: Reuses &lt;code&gt;BaseCondition&lt;/code&gt; and &lt;code&gt;BaseSql&lt;/code&gt; — no duplicate logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outcome&lt;/strong&gt;: &lt;strong&gt;Zero SQL to write&lt;/strong&gt; for standard single‑table operations, matching ORM convenience where it actually makes sense.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4.4 From Manual String Concatenation to Semantic Units
&lt;/h3&gt;

&lt;p&gt;Traditional JDBC or MyBatis often rely on manual string concatenation or XML tags — error‑prone and verbose.&lt;/p&gt;

&lt;p&gt;SQL‑First introduces semantic condition methods that turn each filter into a clear, self‑contained unit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;and(fieldSql, value)&lt;/code&gt; → generic equality condition, auto‑prepends &lt;code&gt;AND&lt;/code&gt; and adds a placeholder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;and(fieldSql, value, matchMode)&lt;/code&gt; → built‑in prefix/suffix/full wildcard support — no manual &lt;code&gt;%&lt;/code&gt; concatenation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;in(fieldSql, array)&lt;/code&gt; → auto‑expands to &lt;code&gt;IN (?, ?, ?)&lt;/code&gt; and handles array parameters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;add(sqlFragment, booleanFlag)&lt;/code&gt; → conditionally includes a SQL snippet based on a business rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contrast&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Traditional Approach&lt;/th&gt;
&lt;th&gt;SQL‑First Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multiple &lt;code&gt;if&lt;/code&gt; checks scattered across code&lt;/td&gt;
&lt;td&gt;Single &lt;code&gt;add()&lt;/code&gt; line per condition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual parameter list management&lt;/td&gt;
&lt;td&gt;Automatic parameter collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String concatenation with risk of syntax errors&lt;/td&gt;
&lt;td&gt;Semantic, IDE‑friendly method calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Practical Engineering Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1 Where to Keep SQL Strings: Constants vs. Inline
&lt;/h3&gt;

&lt;p&gt;Based on the dynamic gradient, we recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static SQL&lt;/strong&gt; (fixed joins, stable table structures) → &lt;strong&gt;&lt;code&gt;static final&lt;/code&gt; constants&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Improves readability and reusability. Performance difference is negligible — JVM string optimisations + JDBC prepared statements + database plan caching make it a non‑issue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dynamic SQL&lt;/strong&gt; (branching, conditional assembly) → &lt;strong&gt;written directly inside the DAO method&lt;/strong&gt;, using plain Java logic.&lt;br&gt;&lt;br&gt;
Java’s imperative capabilities are far more expressive than XML tags or OGNL, making complex dynamic logic much more maintainable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.2 Code Volume: Why It’s About 1/3 to 1/4 of Traditional Frameworks
&lt;/h3&gt;

&lt;p&gt;The SQL‑First architecture eliminates three broad categories of boilerplate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MyBatis’s fragmented XML files, endless dynamic tags, and OGNL expressions.&lt;/li&gt;
&lt;li&gt;ORM’s massive annotation lists, chainable DSLs, and relationship configuration.&lt;/li&gt;
&lt;li&gt;Repeated null‑checks and parameter‑assembly logic across multiple query methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The reduction is not about cutting business logic — it’s about removing accidental complexity imposed by the framework.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 Startup and Runtime Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reflection&lt;/strong&gt; is used &lt;strong&gt;only once at application startup&lt;/strong&gt; to resolve entity metadata. At runtime, there is zero reflection overhead — performance is effectively identical to Spring JDBC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bean scope&lt;/strong&gt;: &lt;code&gt;BaseDao&lt;/code&gt; is prototype‑scoped to isolate metadata per entity type — a deliberate design choice with no performance penalty.&lt;/li&gt;
&lt;li&gt;Batch handling: the framework provides basic batch capabilities; for extremely large datasets, applications should implement their own batching strategies (which is standard practice across all persistence tools).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5.4 Extension: Leveraging Spring’s Native Ecosystem
&lt;/h3&gt;

&lt;p&gt;Instead of a custom plugin system, SQL‑First frameworks embrace &lt;strong&gt;Spring AOP&lt;/strong&gt; for cross‑cutting concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data permissions&lt;/li&gt;
&lt;li&gt;Field masking / desensitisation&lt;/li&gt;
&lt;li&gt;SQL audit logging&lt;/li&gt;
&lt;li&gt;Multi‑tenancy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extensions are written in plain Java + Spring, with no framework‑specific learning curve.&lt;/li&gt;
&lt;li&gt;Caching, read/write splitting, multi‑datasource, and distributed transaction support all come from Spring and middleware — maintaining clear responsibility boundaries.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Beyond Java: A Cross‑Language Paradigm
&lt;/h2&gt;

&lt;p&gt;SQL is language‑agnostic, and the SQL‑First paradigm has been successfully implemented across &lt;strong&gt;8 mainstream backend languages&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Underlying Driver&lt;/th&gt;
&lt;th&gt;Implementation Form&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Spring JDBC&lt;/td&gt;
&lt;td&gt;simple‑dao and similar frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;Dapper&lt;/td&gt;
&lt;td&gt;Condition + Dao layered pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go / Rust&lt;/td&gt;
&lt;td&gt;sqlx&lt;/td&gt;
&lt;td&gt;Semantic condition assembly + native SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;SQLAlchemy Core&lt;/td&gt;
&lt;td&gt;Condition‑based semantic units&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP / Node.js / C++&lt;/td&gt;
&lt;td&gt;Native DB drivers&lt;/td&gt;
&lt;td&gt;Same layering philosophy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All cross‑language implementations follow the same core rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Condition‑layer methods are semantically consistent across languages.&lt;/li&gt;
&lt;li&gt;SQL remains fully transparent; native exceptions are passed through.&lt;/li&gt;
&lt;li&gt;Single‑table CRUD is automated; complex SQL is hand‑written.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;This demonstrates that SQL‑First is not a single framework, but a reusable software engineering pattern that transcends language boundaries.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. When to Choose SQL‑First (and When Not To)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Where It Excels
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise applications&lt;/strong&gt; with heavy multi‑table joins, aggregations, and reporting requirements (e.g., ERP, campus management, government systems, microservices).&lt;/li&gt;
&lt;li&gt;Teams that value &lt;strong&gt;uniform coding standards&lt;/strong&gt;, low maintenance overhead, and want to avoid XML/DSL sprawl.&lt;/li&gt;
&lt;li&gt;Long‑lived, multi‑developer projects where SQL expertise is already present and DBA involvement is frequent.&lt;/li&gt;
&lt;li&gt;Projects already in the Spring ecosystem that prefer &lt;strong&gt;minimal third‑party plugin dependencies&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7.2 Comparative Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Weaknesses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ORM (JPA/Hibernate)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quick for simple CRUD; automatic schema generation&lt;/td&gt;
&lt;td&gt;Semantic mismatch; opaque SQL; N+1 problems; steep learning curve for complex queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MyBatis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full SQL control; widely known&lt;/td&gt;
&lt;td&gt;XML/annotation fragmentation; dynamic tag complexity; steep extension barrier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plain Spring JDBC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Maximum control; minimal abstraction&lt;/td&gt;
&lt;td&gt;Excessive boilerplate for conditions; manual parameter management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQL‑First (e.g., simple‑dao)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native SQL power + condition automation; single‑table CRUD; Spring‑native&lt;/td&gt;
&lt;td&gt;Requires developers to be comfortable writing SQL (which, for backend engineers, should be a core skill)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  8. Final Thoughts: Back to the Essence of Persistence Design
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge the gap&lt;/strong&gt;: 3GL and 4GL are different worlds. &lt;strong&gt;Full ORM is an over‑ambitious abstraction&lt;/strong&gt;; MyBatis adds too much accidental complexity for what it actually does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect both languages&lt;/strong&gt;: Let SQL handle set operations natively. Let Java handle dynamic logic with its full expressive power. Build a thin bridge, not a wall.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep it simple&lt;/strong&gt;: A three‑layer architecture with semantic condition units unifies single‑table and multi‑table queries, eliminates duplication, and enforces consistency.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The best persistence framework is the one you barely notice while writing business logic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Let SQL be SQL. Let Java be Java. Let the bridge be minimal and transparent.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the ultimate goal of the SQL‑First paradigm.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;This article was adapted from internal design discussions and production experience across multiple enterprise projects over several years. The &lt;code&gt;simple-dao&lt;/code&gt; family of repositories (links at the top) provides a working implementation of these ideas in Java.&lt;/p&gt;

&lt;p&gt;If you’ve encountered similar pain points with ORM or MyBatis, or if you’re simply curious about alternative persistence models, we’d love to hear your feedback. Open an issue, start a discussion, or explore the demos — contributions and ideas are always welcome.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;License&lt;/strong&gt;: Apache 2.0&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Originally published&lt;/strong&gt;: 2026-06-20&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>database</category>
      <category>java</category>
      <category>sql</category>
    </item>
    <item>
      <title>2 . SimpleDAO From Zero To Production: Multi-table Joins &amp; Optimized Pagination</title>
      <dc:creator>Z. Gao</dc:creator>
      <pubDate>Fri, 19 Jun 2026 11:07:57 +0000</pubDate>
      <link>https://dev.to/sqlfirst_dev/simpledao-from-zero-to-production-multi-table-joins-optimized-pagination-jla</link>
      <guid>https://dev.to/sqlfirst_dev/simpledao-from-zero-to-production-multi-table-joins-optimized-pagination-jla</guid>
      <description>&lt;h2&gt;
  
  
  GitHub (Overseas Access Friendly)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Core Framework: &lt;a href="https://github.com/gzz2017gzz/simple-dao" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;System Base Starter: &lt;a href="https://github.com/gzz2017gzz/simple-dao-starter" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code Generator: &lt;a href="https://github.com/gzz2017gzz/simple-dao-coder" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-coder&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Production Demo Project: &lt;a href="https://github.com/gzz2017gzz/simple-dao-demo" rel="noopener noreferrer"&gt;https://github.com/gzz2017gzz/simple-dao-demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Supplementary Note
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;simple-dao-starter&lt;/code&gt; &amp;amp; &lt;code&gt;simple-dao-demo&lt;/code&gt; Gitee pages failed to parse when fetching details, but the repository URLs remain fully valid for cloning and browsing.&lt;/li&gt;
&lt;li&gt;The core framework &amp;amp; code generator Gitee READMEs are complete, containing full feature comparison tables, configuration docs, performance benchmarks and code samples for reference.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full Tutorial Series: SimpleDAO From Zero To Production
&lt;/h2&gt;

&lt;p&gt;This episode focuses on &lt;strong&gt;multi-table join queries + pagination&lt;/strong&gt;. All SQL is native, zero XML, high-performance, only basic Java knowledge required to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;No advanced framework experience needed. Only the skills below are required:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;✅ Must-Have Skills&lt;/th&gt;
&lt;th&gt;❌ Not Required At All&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic Java (Class, Interface, Annotation, Generics)&lt;/td&gt;
&lt;td&gt;Any MyBatis / Hibernate experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard SQL (SELECT, JOIN, WHERE clauses)&lt;/td&gt;
&lt;td&gt;XML config, dynamic OGNL tags, plugin development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot basics (Datasource config, DI)&lt;/td&gt;
&lt;td&gt;Spring Boot auto-configuration internals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run Maven projects in IDE&lt;/td&gt;
&lt;td&gt;Advanced Maven modules &amp;amp; parent POM logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Full Series Roadmap
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Episode · Title&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Core Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01 · Single-table CRUD + Audit Fields + Soft Delete&lt;/td&gt;
&lt;td&gt;~6 mins&lt;/td&gt;
&lt;td&gt;Zero-code single-table operations, auto-filled audit columns, built-in soft delete logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02 · Multi-table Joins + Pagination&lt;/td&gt;
&lt;td&gt;~5 mins&lt;/td&gt;
&lt;td&gt;Unified API for single/multi-table queries, no ResultMap required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03 · Advanced Conditions: IN Clauses &amp;amp; Subqueries&lt;/td&gt;
&lt;td&gt;~5 mins&lt;/td&gt;
&lt;td&gt;Drop-in replacement for MyBatis &lt;code&gt;&amp;lt;foreach&amp;gt;&lt;/code&gt;, clean dynamic SQL assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04 · Complex Multi-table Joins &amp;amp; Filter Logic&lt;/td&gt;
&lt;td&gt;~5 mins&lt;/td&gt;
&lt;td&gt;Reusable template for multi-table range filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05 · Reporting Queries: GROUP BY &amp;amp; Aggregate Functions&lt;/td&gt;
&lt;td&gt;~6 mins&lt;/td&gt;
&lt;td&gt;Full native SQL support for reports, no ORM restrictions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06 · Merge Multiple Condition Groups with mergeParams&lt;/td&gt;
&lt;td&gt;~6 mins&lt;/td&gt;
&lt;td&gt;Decouple &amp;amp; reuse filter logic for complex statistical dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07 · Multi-tenancy &amp;amp; Data Permissions via AOP&lt;/td&gt;
&lt;td&gt;~7 mins&lt;/td&gt;
&lt;td&gt;Spring native AOP instead of MyBatis interceptors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08 · Data Masking &amp;amp; Audit Extension — No Framework Lock-In&lt;/td&gt;
&lt;td&gt;~7 mins&lt;/td&gt;
&lt;td&gt;Fix the flawed layered design of MyBatis persistence-level data masking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Episode Goals
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Implement paginated join queries between &lt;code&gt;bus_order&lt;/code&gt; (order table) and &lt;code&gt;sys_user&lt;/code&gt; (user table)&lt;/li&gt;
&lt;li&gt;Learn to pass cross-table filters via the &lt;code&gt;add()&lt;/code&gt; utility method&lt;/li&gt;
&lt;li&gt;Understand SimpleDAO’s high-performance COUNT calculation logic&lt;/li&gt;
&lt;li&gt;Compare with traditional ORMs to see the simplicity of the SQL-First paradigm&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Standard Project Structure
&lt;/h3&gt;

&lt;p&gt;Standard Spring Boot layout, key files listed below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;demo02_join_query/
├── pom.xml                # Maven dependency config
└── src/main/
    ├── java/example/
    │   ├── DemoApplication.java  # Spring boot main class
    │   └── order/               # Order business module
    │       ├── Order.java       # Database entity
    │       ├── OrderVO.java     # DTO for join query results
    │       ├── OrderCond.java   # Query filter class
    │       └── OrderDao.java    # Data access layer
    └── resources/
        ├── application.yml      # Datasource configuration
        └── schema.sql           # Table DDL &amp;amp; test seed data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Minimal Core Dependencies
&lt;/h3&gt;

&lt;p&gt;Only four lightweight dependencies, no bloated transitive libraries, works out of the box:&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.springframework.boot&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;spring-boot-starter-jdbc&lt;span class="nt"&gt;&amp;lt;/artifactId&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;com.simple&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;simple-dao&lt;span class="nt"&gt;&amp;lt;/artifactId&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;com.h2database&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;h2&lt;span class="nt"&gt;&amp;lt;/artifactId&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.projectlombok&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;lombok&lt;span class="nt"&gt;&amp;lt;/artifactId&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;ul&gt;
&lt;li&gt;
&lt;code&gt;spring-boot-starter-jdbc&lt;/code&gt;: Native Spring JDBC foundation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simple-dao&lt;/code&gt;: Core persistence framework for CRUD, pagination &amp;amp; dynamic filters&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;h2&lt;/code&gt;: In-memory database, no external database server needed for local testing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lombok&lt;/code&gt;: Reduce boilerplate getter/setter/toString code for entities&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Datasource Configuration (application.yml)
&lt;/h3&gt;

&lt;p&gt;Standard Spring Boot datasource format — swap URL/driver class to switch MySQL, PostgreSQL, Oracle, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;datasource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1&lt;/span&gt;  &lt;span class="c1"&gt;# In-memory H2 database&lt;/span&gt;
    &lt;span class="na"&gt;driver-class-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org.h2.Driver&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;schema-locations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;classpath:schema.sql&lt;/span&gt;  &lt;span class="c1"&gt;# Auto-run table creation SQL on startup&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Table Schema &amp;amp; Test Seed Data (schema.sql)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Table Definitions
&lt;/h4&gt;

&lt;p&gt;Two tables with business columns + auto-managed audit columns (creation timestamps, soft delete flag):&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;-- User table (sys_user)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;sys_user&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;-- Username&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;-- User age&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- Contact email&lt;/span&gt;
    &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;-- Mobile number&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;-- Password hash&lt;/span&gt;
    &lt;span class="n"&gt;sex&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                        &lt;span class="c1"&gt;-- Gender: 1=Male, 2=Female&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;-- Account status: 1=Active, 0=Disabled&lt;/span&gt;
    &lt;span class="n"&gt;create_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;create_by&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;update_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;update_by&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dr&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;                &lt;span class="c1"&gt;-- Soft delete flag (0 = active)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Order table (bus_order)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;bus_order&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order_no&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="c1"&gt;-- Unique order serial number&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- Foreign key linking to sys_user.id&lt;/span&gt;
    &lt;span class="n"&gt;total_amount&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- Order total value&lt;/span&gt;
    &lt;span class="n"&gt;order_status&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;-- Order state: 0=Unpaid, 1=Paid&lt;/span&gt;
    &lt;span class="n"&gt;create_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;create_by&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;update_time&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;update_by&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dr&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;                  &lt;span class="c1"&gt;-- Soft delete flag&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Test Seed Data
&lt;/h4&gt;

&lt;p&gt;3 sample users + 4 sample orders for join query testing:&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;-- Insert test users&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;sys_user&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Zhang San'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'zhangsan@test.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'13800138001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e10adc3949ba59abbe56e057f20f883e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Li Si'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lisi@test.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'13800138002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e10adc3949ba59abbe56e057f20f883e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Wang Wu'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wangwu@test.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'13800138003'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'e10adc3949ba59abbe56e057f20f883e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert test orders&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;bus_order&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;order_no&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ORD2026001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1999&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ORD2026002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;299&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ORD2026003'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;399&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ORD2026004'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1299&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Core Code Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Base Entity: Order (Maps to bus_order table)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@Table&lt;/code&gt; binds the class to database table name, &lt;code&gt;@Id&lt;/code&gt; marks primary key. Audit columns can be hidden from API docs with &lt;code&gt;@Schema(hidden=true)&lt;/code&gt;:&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;@Data&lt;/span&gt;
&lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Order Database Entity"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bus_order"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Primary Key ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Unique Order Serial Number"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderNo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Associated User ID"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Omitted other business fields: totalAmount, orderStatus, etc.&lt;/span&gt;

    &lt;span class="c1"&gt;// Auto-managed audit columns, hidden from frontend docs&lt;/span&gt;
    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;createTime&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;createBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Soft delete marker&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Join Query DTO: OrderVO
&lt;/h3&gt;

&lt;p&gt;Extends the base &lt;code&gt;Order&lt;/code&gt; entity, adds joined user table fields for multi-table query results:&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;@Getter&lt;/span&gt;
&lt;span class="nd"&gt;@Setter&lt;/span&gt;
&lt;span class="nd"&gt;@ToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callSuper&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;// Print parent class fields in logs&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderVO&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Customer Full Name"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Maps to aliased u.name from join SQL&lt;/span&gt;

    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Customer Mobile Number"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userPhone&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Maps to aliased u.phone&lt;/span&gt;

    &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Customer Contact Email"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userEmail&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Maps to aliased u.email&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: DTO field names must match column aliases defined in your JOIN SQL (e.g. &lt;code&gt;u.name user_name&lt;/code&gt;), the framework auto-maps returned columns to VO properties.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. DAO Layer: OrderDao (Core Join &amp;amp; Pagination Logic)
&lt;/h3&gt;

&lt;p&gt;Core pattern: Raw LEFT JOIN SQL + single &lt;code&gt;page()&lt;/code&gt; method to handle full pagination workflow&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;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderDao&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseDao&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Static native JOIN SQL: Order table LEFT JOIN User table to fetch combined data&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="no"&gt;SQL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
        SELECT t.id, t.order_no, t.user_id, t.total_amount, t.order_status,
               t.create_time, t.dr,
               u.name user_name, u.phone user_phone, u.email user_email
        FROM bus_order t
        LEFT JOIN sys_user u ON t.user_id = u.id
        """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Paginated join query for orders + linked user information
     * @param cond Filter conditions object
     * @return Paginated result wrapped in OrderVO DTO
     */&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;pageJoin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderCond&lt;/span&gt; &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// page() auto-applies filters, generates optimized COUNT query, appends LIMIT offset&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;page&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderVO&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="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Key Features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Full control over raw SQL — choose LEFT JOIN / INNER JOIN based on business requirements, no hidden framework rewrite logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page()&lt;/code&gt; signature: Raw SQL string + condition object + result DTO class, returns fully paginated data in one call&lt;/li&gt;
&lt;li&gt;Works for any number of joined tables; only modify the base SQL text with zero extra code changes&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Filter Class: OrderCond (Cross-table filter chaining)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;addCondition()&lt;/code&gt; method uses the &lt;code&gt;add()&lt;/code&gt; utility to safely chain cross-table filters in one line each&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;@Data&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderCond&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseCondition&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter fields covering both order and user table columns&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderNo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// Match order serial number&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// Match linked user ID on order&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Fuzzy match customer name&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;userPhone&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// Exact match customer phone&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;orderNos&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// Multiple order numbers for IN clause&lt;/span&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;addCondition&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Bidirectional fuzzy match (%value%) for order number, mode=3&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND t.order_no LIKE ?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderNo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 2. Exact equality filter for linked user ID&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND t.user_id = ?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 3. Negative fuzzy match (NOT LIKE %value%) for username&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND u.name NOT LIKE ?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 4. Exact match for customer phone number&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND u.phone = ?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userPhone&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 5. IN clause for array of order serial numbers&lt;/span&gt;
        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND t.order_no IN "&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderNos&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;blockquote&gt;
&lt;p&gt;&lt;code&gt;add()&lt;/code&gt; Method Core Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First argument: Raw SQL filter fragment (supports any table aliases like &lt;code&gt;t.&lt;/code&gt; / &lt;code&gt;u.&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Second argument: Filter value; the entire condition is automatically skipped if the value is null (no manual null checks required)&lt;/li&gt;
&lt;li&gt;Optional third parameter: Fuzzy search mode (1 = suffix match &lt;code&gt;xxx%&lt;/code&gt;, 2 = prefix match &lt;code&gt;%xxx&lt;/code&gt;, 3 = full wrap &lt;code&gt;%xxx%&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;IN clause shortcut: Only write &lt;code&gt;AND column IN&lt;/code&gt; — the framework auto-expands the array to parameterized &lt;code&gt;IN (?, ?, ...)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Service Layer Usage &amp;amp; Runtime Output
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Service Invocation Demo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OrderDao&lt;/span&gt; &lt;span class="n"&gt;orderDao&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;demoJoinPage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Build filter: Username contains "Zhang" + match a set of target order numbers&lt;/span&gt;
        &lt;span class="nc"&gt;OrderCond&lt;/span&gt; &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderCond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Zhang"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderNos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]{&lt;/span&gt;&lt;span class="s"&gt;"ORD2026001"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ORD2026003"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ORD2026004"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Run multi-table paginated query (defaults to page 1, 10 records per page)&lt;/span&gt;
        &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderVO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pageJoin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Print pagination output to logs&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Total matching records: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRowCount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDataList&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vo&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Combined Order+User Data: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vo&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;h3&gt;
  
  
  2. Full Generated SQL (Printed in Application Logs)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Optimized COUNT query (rewrites SELECT clause instead of wrapping subquery)
[INFO] SELECT COUNT(1) FROM bus_order t LEFT JOIN sys_user u ON t.user_id = u.id WHERE u.name LIKE '%Zhang%' AND t.order_no IN ('ORD2026001', 'ORD2026003', 'ORD2026004')

-- Paginated data query with auto-generated LIMIT offset
[INFO] SELECT t.id, t.order_no, ..., u.name user_name, ... 
FROM bus_order t LEFT JOIN sys_user u ON t.user_id = u.id 
WHERE u.name LIKE '%Zhang%' AND t.order_no IN ('ORD2026001', 'ORD2026003', 'ORD2026004') 
LIMIT 0,10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Performance Benefit for COUNT Queries:&lt;br&gt;
Instead of wrapping your full join SQL inside a subquery &lt;code&gt;SELECT COUNT(*) FROM (...)&lt;/code&gt;, SimpleDAO directly rewrites the top-level SELECT clause to &lt;code&gt;COUNT(1) FROM&lt;/code&gt;. This avoids creating intermediate result sets and leverages database indexes fully — critical performance gain under high concurrent traffic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Head-To-Head: MyBatis vs SimpleDAO Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Comparison 1: Fuzzy LIKE Filter on Username
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MyBatis XML Implementation&lt;/strong&gt;&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;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"userName != null"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  AND u.name LIKE CONCAT('%', #{userName}, '%')
&lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downsides: Switch between XML markup, OGNL null checks, and native SQL functions; scattered code across separate files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SimpleDAO Implementation&lt;/strong&gt;&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;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND u.name LIKE ?"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantage: Single line pure Java, numeric mode defines wrap logic, condition auto-skipped for null values, no manual null validation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Comparison 2: IN Clause Multi-value Matching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MyBatis XML Implementation&lt;/strong&gt;&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;if&lt;/span&gt; &lt;span class="na"&gt;test=&lt;/span&gt;&lt;span class="s"&gt;"orderNos != null and orderNos.size() &amp;gt; 0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  AND t.order_no IN
  &lt;span class="nt"&gt;&amp;lt;foreach&lt;/span&gt; &lt;span class="na"&gt;collection=&lt;/span&gt;&lt;span class="s"&gt;"orderNos"&lt;/span&gt; &lt;span class="na"&gt;item=&lt;/span&gt;&lt;span class="s"&gt;"no"&lt;/span&gt; &lt;span class="na"&gt;open=&lt;/span&gt;&lt;span class="s"&gt;"("&lt;/span&gt; &lt;span class="na"&gt;separator=&lt;/span&gt;&lt;span class="s"&gt;","&lt;/span&gt; &lt;span class="na"&gt;close=&lt;/span&gt;&lt;span class="s"&gt;")"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    #{no}
  &lt;span class="nt"&gt;&amp;lt;/foreach&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/if&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downsides: Nested tag boilerplate, many mandatory tag attributes; missing any attribute triggers runtime exceptions, hard to maintain for complex projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SimpleDAO Implementation&lt;/strong&gt;&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;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AND t.order_no IN "&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderNos&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Advantage: Pass raw array directly, framework auto-generates parameterized placeholders, no loop tag syntax required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Episode Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Full Native Join Control&lt;/strong&gt;: Write raw LEFT / INNER JOIN SQL, fully control table relationships with no hidden framework rewriting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-table Filter Chaining&lt;/strong&gt;: The &lt;code&gt;add()&lt;/code&gt; utility safely applies filters for any joined table column, automatically skips null values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized Pagination Count&lt;/strong&gt;: Rewrites SELECT clause instead of wrapping subqueries for index-friendly counting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Black Box Abstraction&lt;/strong&gt;: Strict SQL-First design, no XML config, no proprietary dynamic tag syntax&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coexistence Compatibility&lt;/strong&gt;: Works side-by-side with MyBatis; you can incrementally migrate DAO layers project by project&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Episode Preview
&lt;/h2&gt;

&lt;p&gt;Advanced Filter Logic: IN Clauses &amp;amp; Subqueries. Master the &lt;code&gt;addIn()&lt;/code&gt; helper and reusable filter classes to simplify complicated report queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Access
&lt;/h2&gt;

&lt;p&gt;All demo source code linked in pinned comment — clone &amp;amp; run with zero extra configuration. If this tutorial helped you, leave a like &amp;amp; save this post, follow for more SimpleDAO production tutorials!&lt;/p&gt;




</description>
      <category>springboot</category>
      <category>spring</category>
      <category>jpa</category>
      <category>simpledao</category>
    </item>
    <item>
      <title>1 . SimpleDAO From Zero To Production: Single-table CRUD, Audit Fields &amp; Soft Delete</title>
      <dc:creator>Z. Gao</dc:creator>
      <pubDate>Thu, 18 Jun 2026 03:06:12 +0000</pubDate>
      <link>https://dev.to/sqlfirst_dev/production-crud-in-java-without-the-framework-tax-2n82</link>
      <guid>https://dev.to/sqlfirst_dev/production-crud-in-java-without-the-framework-tax-2n82</guid>
      <description>&lt;h2&gt;
  
  
  A practical walkthrough of SQL-First persistence: no XML, no Mapper interfaces, no generated queries.
&lt;/h2&gt;




&lt;p&gt;I maintain a Java backend that handles ~1M requests/day. For persistence, we used to run MyBatis. The XML was manageable at first, then it wasn't. Dynamic conditions became &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; tag soup. A simple join query needed three files and two languages.&lt;/p&gt;

&lt;p&gt;We switched to a simpler approach. Here's how it works for the most common case: single-table CRUD.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Java 21+&lt;/li&gt;
&lt;li&gt;Spring Boot (any 3.x)&lt;/li&gt;
&lt;li&gt;A database (H2 for the demo, MySQL/PostgreSQL for production)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No XML parser, no code generator, no annotation processor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pom.xml
src/main/java/example/
  DemoApplication.java
  user/
    User.java        -- entity
    UserDao.java     -- data access
    UserCond.java    -- query conditions
src/main/resources/
  application.yml
  schema.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three Java files for a complete CRUD API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Entity
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Data&lt;/span&gt; &lt;span class="nd"&gt;@Builder&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sys_user"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other fields&lt;/span&gt;

    &lt;span class="c1"&gt;// These four are auto-managed:&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;createTime&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;createBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt; &lt;span class="n"&gt;updateTime&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;updateBy&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Byte&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 0 = active, 1 = soft-deleted&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@Table&lt;/code&gt; maps to the database table. &lt;code&gt;@Id&lt;/code&gt; marks the primary key (Snowflake ID by default). The audit fields and soft-delete marker are handled automatically—you don't set them in business code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DAO
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDao&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseDao&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Empty. All CRUD methods inherited.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;BaseDao&lt;/code&gt; provides &lt;code&gt;save&lt;/code&gt;, &lt;code&gt;saveBatch&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;findById&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;page&lt;/code&gt;, &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;exists&lt;/code&gt;. For single-table operations, this is all you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conditions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Getter&lt;/span&gt; &lt;span class="nd"&gt;@Setter&lt;/span&gt; &lt;span class="nd"&gt;@Builder&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserCond&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseCondition&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;ageMin&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;ageMax&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Byte&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&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;addCondition&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name LIKE"&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="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 3 = %value%&lt;/span&gt;
        &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"age &amp;gt;="&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ageMin&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"age &amp;lt;="&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ageMax&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dr ="&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;in&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;ids&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;Each &lt;code&gt;and()&lt;/code&gt; line is one condition. If the parameter is null, the condition is skipped. No &lt;code&gt;&amp;lt;if&amp;gt;&lt;/code&gt; tags, no OGNL, no XML.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;3&lt;/code&gt; in &lt;code&gt;and("name LIKE", name, 3)&lt;/code&gt; means "wrap with % on both sides". &lt;code&gt;1&lt;/code&gt; = suffix, &lt;code&gt;2&lt;/code&gt; = prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Autowired&lt;/span&gt; &lt;span class="nc"&gt;UserDao&lt;/span&gt; &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;demo&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Insert&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&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="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"john@example.com"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// id, createTime, createBy, dr auto-filled&lt;/span&gt;

    &lt;span class="c1"&gt;// Query by ID&lt;/span&gt;
    &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Paginated search&lt;/span&gt;
    &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;UserCond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&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="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;ageMin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;ageMax&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Update&lt;/span&gt;
    &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// updateTime, updateBy auto-filled&lt;/span&gt;

    &lt;span class="c1"&gt;// Soft delete (dr=1, not DELETE)&lt;/span&gt;
    &lt;span class="n"&gt;userDao&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&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;
  
  
  The SQL (from logs)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- save()&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;sys_user&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;create_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;create_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3679201737291333632&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'John'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2026-04-10 13:14:10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- page()&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys_user&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&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;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%John%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&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;t&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys_user&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&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;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%John%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; 
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;-- delete() with dr field present&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;sys_user&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3679201737291333632&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL in the logs is the SQL that runs. No hidden transformations, no proxy-generated queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Isn't
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not an ORM&lt;/strong&gt;: We don't map object graphs or handle relationships automatically. For joins, you write the SQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not type-safe SQL&lt;/strong&gt;: If you misspell a column name, you find out at runtime. The tradeoff is transparency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a replacement for everything&lt;/strong&gt;: If you need complex caching, second-level cache, or distributed transactions, Spring has other tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What This Is
&lt;/h2&gt;

&lt;p&gt;A thin bridge between Java and SQL. The framework handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parameter collection and null-checking&lt;/li&gt;
&lt;li&gt;Audit field auto-fill&lt;/li&gt;
&lt;li&gt;Soft-delete logic&lt;/li&gt;
&lt;li&gt;Pagination (count + limit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The SQL&lt;/li&gt;
&lt;li&gt;The business logic&lt;/li&gt;
&lt;li&gt;The optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Episode 02: Multi-table joins with the same API → [link]&lt;/p&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gzz2017gzz/simple-dao-demo" rel="noopener noreferrer"&gt;github.com/gzz2017gzz/simple-dao-demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>spring</category>
      <category>springboot</category>
      <category>simpledao</category>
    </item>
  </channel>
</rss>
