Easy-Query: A Strongly-Typed Java ORM with LINQ-Style Queries and Zero Dependencies
This article introduces Easy-Query, a Java ORM framework inspired by .NET's LINQ that enables strongly-typed, chainable query expressions for 90% of database scenarios, demonstrating queries, joins, subqueries, pagination, streaming, dynamic conditions, grouping, and encryption with generated SQL examples.
Background
After several years of Java development, the author sought a .NET-like ORM that allows writing intuitive SQL through strong-typed syntax for 90% of business scenarios. MyBatis-Plus initially seemed promising but proved incomplete: most scenarios lack expression or strong-typed support, causing features like soft-delete to break with joins, and handwritten SQL makes configurations unintelligent — effectively reducing it to a SQL helper. MyBatis-Plus-Join fares worse, requiring constant trial-and-error to "stitch" desired statements. Consequently, the author built Easy-Query, porting the mature .NET ORM ecosystem's chainable expression semantics to Java.
Query Capabilities
All examples use a fluent queryable(Entity.class) entry point with lambda expressions referencing entity getters (e.g., Topic::getId) for compile-time safety.
Basic Queries
First or null: .where(o -> o.eq(Topic::getId, "123")).firstOrNull() generates
SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ? LIMIT 1.
Single or null (asserts ≤1 row): Same WHERE but without LIMIT; throws if multiple rows match.
List: .toList() returns all matching rows.
Custom columns: .select(o -> o.column(Topic::getId).column(Topic::getTitle)) projects only id, title.
Pagination: .toPageResult(1, 20) runs a COUNT query then the paged SELECT ... LIMIT 20.
Advanced Queries
Subquery as derived table: A Queryable can be select -ed into an anonymous table and then leftJoin -ed, producing a nested SELECT ... FROM (subquery) t1 LEFT JOIN ....
EXISTS subquery:
.where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId))))correlates the outer Topic alias t inside the inner BlogEntity query, yielding
WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`).
Multi-table JOIN:
.leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))automatically appends the joined table's soft-delete filter ( t1.`deleted` = ?) to the ON clause.
Streaming large results: .toStreamResult() returns a JdbcStreamResult iterable for memory-efficient processing; the example iterates 100+ rows with assertions.
Custom VO mapping: Multiple joins (two- and three-parameter lambdas) with .select(QueryVO.class, (t, t1, t2) -> ...) maps columns across tables to a DTO using columnAs(Source::getField, VO::getTarget) and then() to switch context.
Dynamic form query: .whereObject(queryRequest) inspects a request DTO's non-null fields (String → LIKE, List → IN, LocalDateTime → <=) and builds the WHERE clause automatically.
Primitive results: .select(String.class, o -> o.column(Topic::getId)).toList() returns List<String>.
Grouping & aggregation:
.groupBy(o -> o.column(Topic::getId)).select(DTO.class, o -> o.columnAs(Topic::getId, DTO::getId).columnCount(Topic::getId, DTO::getIdCount))produces
SELECT t.`id` AS `id`, COUNT(t.`id`) AS `idCount` ... GROUP BY t.`id`; groupKeysAs shortcut aliases group keys by index.
Native SQL fragment:
.where(o -> o.sqlNativeSegment("regexp_like({0},{1})", it -> it.expression(H2BookTest::getPrice).value("^Ste(v|ph)en$")))embeds dialect-specific functions while keeping parameter binding.
Database Function Columns
Easy-Query supports transparent column-level SQL functions (e.g., Base64 encode/decode, PostGIS geography types) so the entity holds plain Java types while the generated SQL applies the conversion. Documentation: https://xuejm.gitee.io/easy-query-doc/guide/adv/column-sql-func-auto.html
High-Performance Encryption with LIKE Support
Column-level encryption/decryption that still permits LIKE queries on encrypted data, with optional emoji-safe modes. Documentation: https://xuejm.gitee.io/easy-query-doc/guide/adv/column-encryption.html
Additional Features
Data change tracking & differential updates
Atomic field updates
Sharding (horizontal partitioning)
Zero third-party dependencies
Dual Java/Kotlin support
Repositories
GitHub: https://github.com/xuejmnet/easy-query
Gitee: https://gitee.com/xuejm/easy-query
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
