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