Mastering EasyQuery: A Strongly Typed Java ORM with Real‑World Query Examples

This article explains why the author created the EasyQuery ORM for Java, demonstrates a wide range of query patterns—including single‑record, pagination, joins, subqueries, streaming, custom VO mapping, dynamic conditions, grouping, native SQL, function columns, and high‑performance encryption—while providing complete code snippets and links to documentation and source repositories.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Mastering EasyQuery: A Strongly Typed Java ORM with Real‑World Query Examples

Background

After several years of using Java and searching for a .NET‑style ORM that allows intuitive, strongly‑typed SQL generation, the author found existing solutions like MyBatis‑Plus insufficient for many scenarios, especially when dealing with soft deletes, joins, and complex expressions. Consequently, a new ORM named EasyQuery was developed, inspired by the completeness of .NET ORM ecosystems and designed to bring fluent, type‑safe query building to Java.

Basic Queries

Querying the first record:

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

Querying a single record with an assertion:

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

Querying multiple records:

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

Selecting 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> page = easyQuery.queryable(Topic.class)
    .where(o -> o.isNotNull(Topic::getId))
    .toPageResult(1, 20);

Advanced Queries

Transforming an expression into a nested 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();

Subquery example:

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

List<Topic> result = easyQuery.queryable(Topic.class)
    .where(o -> o.exists(sub.where(q -> q.eq(o, BlogEntity::getId, Topic::getId))))
    .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()) {
    int i = 0;
    for (BlogEntity blog : stream.getStreamIterable()) {
        // assertions verifying each field against expected values
        i++;
    }
} catch (SQLException e) {
    throw new RuntimeException(e);
}

Custom VO Mapping

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 Conditions

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

List<BlogEntity> list = easyQuery.queryable(BlogEntity.class)
    .whereObject(req)
    .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 Queries

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 Segment

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

Database Function Columns

EasyQuery can transparently handle columns that require database functions, such as Base64 decoding or PostgreSQL geographic types, without changing the Java model.

High‑Performance Encryption for LIKE

The library also provides encryption/decryption that works with the SQL LIKE operator, supporting both emoji and non‑emoji data.

Documentation: https://xuejm.gitee.io/easy-query-doc/guide/adv/column-sql-func-auto.html
Encryption guide: https://xuejm.gitee.io/easy-query-doc/guide/adv/column-encryption.html

Project Links

GitHub: https://github.com/xuejmnet/easy-query
Gitee: https://gitee.com/xuejm/easy-query

The article concludes with a brief thank‑you note and a source reference.

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.

BackendJavaSQLdatabaseORMquery buildereasyquery
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

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.