Databases 13 min read

Easy-Query: A High‑Performance Java ORM with Strongly‑Typed Query API

This article introduces Easy‑Query, a Java ORM that provides a strongly‑typed, chainable query syntax covering basic CRUD, pagination, joins, sub‑queries, native SQL fragments, group‑by, custom VO mapping and high‑performance encryption, illustrated with extensive code examples and usage notes.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Easy-Query: A High‑Performance Java ORM with Strongly‑Typed Query API

Background – After years of using Java and searching for a .NET‑like ORM that supports strong‑typed SQL for most scenarios, the author found MyBatis‑Plus insufficient and decided to develop a new ORM inspired by the completeness of .NET ecosystems.

Query Examples

Retrieve the first record:

Topic topic = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "123"))
    .firstOrNull();

Assert at most one record:

Topic topic = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "123"))
    .singleOrNull();

Retrieve a list:

List<Topic> topics = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "123"))
    .toList();

Custom column selection:

Topic topic = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "1"))
    .select(o -> o.column(Topic::getId).column(Topic::getTitle))
    .firstOrNull();

Pagination:

EasyPageResult<Topic> page = easyQuery.queryable(Topic.class)
    .where(o -> o.isNotNull(Topic::getId))
    .toPageResult(1, 20);

Sub‑query as an anonymous table:

Queryable<Topic> query = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "1"))
    .select(Topic.class, o -> o.column(Topic::getId).column(Topic::getTitle));
List<Topic> list = query.leftJoin(Topic.class, (t, t1) -> t.eq(t1, Topic::getId, Topic::getId))
    .where((t, t1) -> { t1.eq(Topic::getId, "123"); t.eq(Topic::getId, "456"); })
    .toList();

Multi‑table left join:

Topic topic = easyQuery.queryable(Topic.class)
    .leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
    .where(o -> o.eq(Topic::getId, "3"))
    .firstOrNull();

Streaming large result sets:

try (JdbcStreamResult<BlogEntity> stream = easyQuery.queryable(BlogEntity.class)
        .where(o -> o.le(BlogEntity::getStar, 100))
        .orderByAsc(o -> o.column(BlogEntity::getCreateTime))
        .toStreamResult()) {
    // iterate stream and assert fields
}

Custom VO mapping with multiple joins:

List<QueryVO> list = easyQuery.queryable(Topic.class)
    .leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
    .leftJoin(SysUser.class, (t, t1, t2) -> t.eq(t2, Topic::getId, SysUser::getId))
    .where(o -> o.eq(Topic::getId, "123"))
    .where((t, t1, t2) -> t.eq(Topic::getId, "123").then(t1).like(BlogEntity::getTitle, "456").then(t2).eq(BaseEntity::getCreateTime, LocalDateTime.now()))
    .select(QueryVO.class, (t, t1, t2) -> t.column(Topic::getId)
        .then(t1).columnAs(BlogEntity::getTitle, QueryVO::getField1)
        .then(t2).columnAs(SysUser::getId, QueryVO::getField2))
    .toList();

Dynamic form‑based query:

BlogQuery2Request query = new BlogQuery2Request();
query.setContent("标题");
query.setPublishTimeEnd(LocalDateTime.now());
query.setStatusList(Arrays.asList(1, 2));
List<BlogEntity> result = easyQuery.queryable(BlogEntity.class)
    .whereObject(query)
    .toList();

Basic type result:

List<String> ids = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "1"))
    .select(String.class, o -> o.column(Topic::getId))
    .toList();

Group‑by query:

List<TopicGroupTestDTO> groups = easyQuery.queryable(Topic.class)
    .where(o -> o.eq(Topic::getId, "3"))
    .groupBy(o -> o.column(Topic::getId))
    .select(TopicGroupTestDTO.class, o -> o.columnAs(Topic::getId, TopicGroupTestDTO::getId)
        .columnCount(Topic::getId, TopicGroupTestDTO::getIdCount))
    .toList();

Native SQL fragment example:

String sql = easyQuery.queryable(H2BookTest.class)
    .where(o -> o.sqlNativeSegment("regexp_like({0},{1})", it -> it.expression(H2BookTest::getPrice)
        .value("^Ste(v|ph)en$")))
    .select(o -> o.columnAll())
    .toSQL();
// Resulting SQL: SELECT id,name,edition,price,store_id FROM t_book_test WHERE regexp_like(price,?)

Database Function Columns – Easy‑Query supports transparent use of database functions (e.g., PostgreSQL geo types) and high‑performance encryption/decryption that works with LIKE patterns, including emoji support.

Additional features mentioned include change‑tracking, atomic updates, sharding, and a bilingual (Java/Kotlin) implementation.

Project links: GitHub | Gitee

At the end, the author encourages readers to share the article, join the architecture community, and follow the public account for more resources.

backendJavaSQLDatabaseORMquery-builderEasy-Query
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login 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.