Skip to content

[Feature Request] Auto-Generate Specifications from Annotated POJOs #3897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
SWQXDBA opened this issue May 21, 2025 · 0 comments
Open

[Feature Request] Auto-Generate Specifications from Annotated POJOs #3897

SWQXDBA opened this issue May 21, 2025 · 0 comments
Labels
status: waiting-for-triage An issue we've not yet triaged

Comments

@SWQXDBA
Copy link
Contributor

SWQXDBA commented May 21, 2025

Feature Request

A very common use case is querying based on forms submitted from the frontend. Users fill out one or more query conditions on the interface. For example, we define a query object for searching Books:

class BookQuery {
    private String bookName;
    private String authorName;
    
    // Getters and setters
}

Then we build the query based on this query object:

Specification<Book> byQuery(BookQuery queryParam) {
    return (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        
        if (StringUtils.isNotBlank(queryParam.getBookName())) {
            predicates.add(cb.like(
                root.get("bookName"), 
                "%" + queryParam.getBookName() + "%"
            ));
        }
        
        if (StringUtils.isNotBlank(queryParam.getAuthorName())) {
            predicates.add(cb.like(
                root.get("author").get("name"), 
                "%" + queryParam.getAuthorName() + "%"
            ));
        }
        
        return cb.and(predicates.toArray(new Predicate[0]));
    };
}

Or by mybatis xml

<!-- BookMapper.xml -->
<mapper namespace="com.example.mapper.BookMapper">
    
    <sql id="bookQueryConditions">
        <where>
            <if test="bookName != null and bookName != ''">
                AND book_name LIKE CONCAT('%', #{bookName}, '%')
            </if>
            <if test="authorName != null and authorName != ''">
                AND EXISTS (
                    SELECT 1 FROM author 
                    WHERE author.id = book.author_id
                    AND author.name LIKE CONCAT('%', #{authorName}, '%')
                )
            </if>
        </where>
    </sql>

    <select id="findByQuery" resultType="com.example.model.Book">
        SELECT * FROM book
        <include refid="bookQueryConditions"/>
        ORDER BY id
    </select>

    <select id="countByQuery" resultType="long">
        SELECT COUNT(*) FROM book
        <include refid="bookQueryConditions"/>
    </select>
</mapper>

In my daily work, a significant amount of time is wasted on this meaningless translation process - simply converting query POJOs into ORM-recognizable expressions.

When we used only JPA, this manifested as Predicates in CriteriaQuery. With Spring Data JPA, it's now wrapped as Specifications. This repetitive boilerplate code adds no business value but consumes considerable development time.

This pattern violates the DRY (Don't Repeat Yourself) principle and significantly impacts development efficiency. We need a standardized approach to eliminate this repetitive translation layer.

I propose a solution to automate this translation process, which I call Annotation-based Dynamic Query. This approach uses annotations to mark query objects, then automatically converts POJOs into ORM-recognizable query conditions through a processor.Core Concept:

By annotating fields in query objects, we can dynamically generate the corresponding query predicates (JPA Specification, MyBatis-plus QueryWrapper, etc.) without manual translation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface QueryCondition {
    String field() default "";       // Target entity field (default: same as query field)
    Operator operator() default Operator.EQ;  // =, LIKE, >, <, etc.
    boolean ignoreNull() default true; // Skip if value is null
}

public enum Operator {
    EQ, LIKE, GT, LT, IN, BETWEEN...
}
public class BookQuery {
    @QueryCondition(operator = Operator.LIKE)
    private String bookName;

    @QueryCondition(field = "author.name", operator = Operator.LIKE)
    private String authorName;
    
    @QueryCondition(operator = Operator.GT, field = "publishDate")
    private LocalDate minPublishDate;
}

We can implement a utility that transforms BookQuery into Specification through annotation reflection. This solution effectively eliminates repetitive and tedious coding while seamlessly integrating with frontend-backend separation architectures.

The workflow becomes straightforward:

Deserialize frontend parameters (e.g., JSON payload) into BookQuery POJO

Automatically convert to Specification

Execute various operations (pagination/list/count queries) with the generated specification

Key Advantages:
✅ Proven Solution - This isn't theoretical. We've successfully implemented this pattern across multiple projects, where it handles over 80% of query cases
✅ Replaces Complex APIs - Eliminates the need for cumbersome Criteria API constructions and error-prone HQL
✅ Full Coverage - Our real-world validation confirms it satisfies most application scenarios
✅ Seamless Integration - Naturally fits modern RESTful APIs with JSON payloads

While this has proven valuable in our projects, I'm uncertain about broader interest. Should the Spring Data maintainers find this enhancement worthwhile, I'd be glad to discuss.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

2 participants