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.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Easy-Query: A Strongly-Typed Java ORM with LINQ-Style Queries and Zero Dependencies

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

shardingDatabase EncryptionEasy-QueryJava ORMLINQ-styleMyBatis AlternativeStrongly-Typed QueriesZero Dependencies
Java Tech Enthusiast
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.