Databases 15 min read

EasyQuery – A High‑Performance Java ORM with Strongly Typed Query API

The article presents EasyQuery, a high‑performance, type‑safe Java ORM inspired by .NET solutions, demonstrating its query capabilities—including single‑record retrieval, pagination, joins, subqueries, streaming results, custom VO mapping, dynamic conditions, and native SQL fragments—while highlighting its advantages over MyBatis‑Plus.

Top Architect
Top Architect
Top Architect
EasyQuery – A High‑Performance Java ORM with Strongly Typed Query API

The author, a senior architect, describes the motivation for creating a new Java ORM called EasyQuery after finding existing solutions like MyBatis‑Plus insufficient for strong‑typed, expressive SQL generation.

1 Background

After years of using Java, the author sought a .NET‑style ORM that could handle 90% of scenarios with strong‑typed syntax, but MyBatis‑Plus fell short due to limited expression support, soft‑delete handling, and lack of true ORM features.

Consequently, a self‑developed ORM was built, borrowing concepts from mature .NET ORMs and providing a fluent, chainable API.

2 Queries

Query first record

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

Query with single‑record assertion

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

Query multiple records

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

Select specific columns

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

Paginated query

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

Expression to sub‑query (anonymous table)

// SELECT `id`,`title` FROM `t_topic` WHERE `id` = ?
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();

Sub‑query example

Queryable<BlogEntity> subQueryable = easyQuery.queryable(BlogEntity.class)
    .where(o -> o.eq(BlogEntity::getId, "1"));

List<Topic> x = easyQuery
    .queryable(Topic.class)
    .where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId))))
    .toList();

Multi‑table 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 set

try (JdbcStreamResult<BlogEntity> streamResult = easyQuery.queryable(BlogEntity.class)
        .where(o -> o.le(BlogEntity::getStar, 100))
        .orderByAsc(o -> o.column(BlogEntity::getCreateTime))
        .toStreamResult()) {
    // iterate streamResult.getStreamIterable()
} catch (SQLException e) {
    throw new RuntimeException(e);
}

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"))
    .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 query based on request object

BlogQuery2Request query = new BlogQuery2Request();
query.setContent("标题");
query.setPublishTimeEnd(LocalDateTime.now());
query.setStatusList(Arrays.asList(1,2));

List<BlogEntity> list = easyQuery.queryable(BlogEntity.class)
    .whereObject(query)
    .toList();

Returning basic types

List<String> list = 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> dtos = 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

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();

The article also mentions additional features such as database‑level encryption/decryption, data change tracking, atomic updates, and sharding support, and provides links to the documentation and the GitHub/Gitee repositories of EasyQuery.

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.

JavadatabaseORMeasyquery
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.