DEV Community

Li
Li

Posted on

easy-query ORM: EF Core for Java

easy-query is a strongly-typed Java ORM framework. It uses APT (Annotation Processing Tool) to generate proxy classes at compile time, enabling strongly-typed property access. Query with user.name().like("xxx") instead of string "name" - the compiler checks field existence, IDE provides auto-completion, filling the gap that Java lacks LINQ.


Five Implicit Features

Assume Company has a one-to-many relationship with SysUser:

1. Implicit Join

Query users where company name contains "�Tech", ordered by company's registered capital descending:

List<SysUser> users = entityQuery.queryable(SysUser.class)
    .where(user -> {
        user.company().name().like("Tech");
    })
    .orderBy(user -> {
        user.company().registerMoney().desc();
        user.birthday().asc();
    })
    .toList();
Enter fullscreen mode Exit fullscreen mode

2. Implicit Subquery

Query companies that have an employee named "test" who was born after 2000:

List<Company> companies = entityQuery.queryable(Company.class)
    .where(company -> {
        // any: exists records matching the condition
        company.users().any(u -> u.name().like("test"));
        // none: no records matching the condition
        company.users().none(u -> u.name().like("test2"));
        // aggregate subquery
        company.users()
            .where(u -> u.name().like("test"))
            .max(u -> u.birthday())
            .gt(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
    })
    .toList();
Enter fullscreen mode Exit fullscreen mode

3. Implicit Grouping

Same query as above, but merges multiple subqueries into a single GROUP JOIN to avoid multiple subqueries:

List<Company> companies = entityQuery.queryable(Company.class)
    .subQueryToGroupJoin(company -> company.users())
    .where(company -> {
        company.users().any(u -> u.name().like("test"));
        company.users()
            .where(u -> u.name().like("test"))
            .max(u -> u.birthday())
            .gt(LocalDateTime.now());
    })
    .toList();
Enter fullscreen mode Exit fullscreen mode

4. Implicit Partition Grouping

Query companies where the youngest employee is named "test":

List<Company> companies = entityQuery.queryable(Company.class)
    .where(company -> {
        company.users().orderBy(u -> u.birthday().desc()).first().name().eq("test");
        company.users().orderBy(u -> u.birthday().desc()).element(0)
            .birthday().lt(LocalDateTime.of(2000, 1, 1, 0, 0, 0));
    })
    .toList();
Enter fullscreen mode Exit fullscreen mode

5. Implicit CASE WHEN

Count users in department A, department B, and average age of department B:

List<Draft3<Long, Long, BigDecimal>> stats = entityQuery.queryable(SysUser.class)
    .select(user -> Select.DRAFT.of(
        user.id().count().filter(() -> user.department().eq("A")),
        user.id().count().filter(() -> user.department().eq("B")),
        user.age().avg().filter(() -> user.department().eq("B"))
    ))
    .toList();
Enter fullscreen mode Exit fullscreen mode

Loading Related Data: include2

Load related data with support for multiple relations and deep nesting in a single declaration:

List<SchoolClass> list = entityQuery.queryable(SchoolClass.class)
    .include2((c, s) -> {
        // Load student addresses (deep nesting)
        c.query(s.schoolStudents().flatElement().schoolStudentAddress());
        // Load teachers with conditions and ordering
        c.query(s.schoolTeachers().where(x -> x.id().isNotNull()).orderBy(x -> x.name().asc()));
    })
    .toList();

// Even deeper nesting: A -> B -> C -> D
M8SaveA a = entityQuery.queryable(M8SaveA.class)
    .whereById("1")
    .include2((c, s) -> {
        c.query(s.m8SaveB().m8SaveC().m8SaveD());
    })
    .singleNotNull();
Enter fullscreen mode Exit fullscreen mode

DTO Auto-Mapping: selectAutoInclude

Similar to Hibernate's Projection, but with automatic population of navigation properties.

@Data
public class UserRoleDTO {
    private String id;
    private String name;

    @Navigate(value = RelationTypeEnum.ManyToMany)
    private List<InternalRole> roles;  // Navigation property

    @Data
    public static class InternalRole {
        private String id;
        private String name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Query with related data populated in one line:

List<UserRoleDTO> users = entityQuery.queryable(SysUser.class)
    .where(u -> u.id().isNotNull())
    .selectAutoInclude(UserRoleDTO.class)
    .toList();
Enter fullscreen mode Exit fullscreen mode

The framework automatically queries the main table and populates navigation properties based on the DTO structure - no manual include required.


Conclusion

easy-query puts significant effort into its strongly-typed query DSL. It performs extensive inference based on object relationships, making it easy to write complex queries.


Links

Top comments (0)