-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[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
Comments
Have you tried using Query by Example? |
Sure! While QueryByExample offers some convenient features, we've found its practical application to be somewhat limited in real-world scenarios. Our experience suggests it may not be the most versatile solution, as it comes with certain functional constraints that can impact implementation flexibility. It requires using an entity as the query parameter, which is unacceptable. The query parameter object and the domain object should clearly be two separate models. In a web scenario, the query object is actually deserialized from a frontend JSON. Therefore, if QueryByExample is to be used, manual translation of the query object into an Example Entity is required. This does not reduce the workload—it merely shifts from writing Criteria to writing an Example Entity and Matcher. For example, if you need to query the creator of data by either name or employee ID (jobNumber): Different properties use AND conditions, while multiple annotations on the same property represent OR conditions. This is the most typical use case—allowing a single attribute to match against multiple columns in the database. @Entity
class Order {
// other properties
@ManyToOne
User createBy;
// A snapshot of the creator's name when the order was created
// (since the user might change their nickname later)
String creatorNameSnapshot;
int createYear;
}
class OrderQuery {
@QueryCondition(value = "createBy.username", operator = Operator.LIKE)
@QueryCondition(value = "createBy.jobNumber", operator = Operator.LIKE)
@QueryCondition(value = "creatorNameSnapshot", operator = Operator.LIKE)
String creator;
@QueryCondition(value = "createYear", operator = Operator.GE)
int createYearAfterAndEqual;
} Query request ==> Auto generated Predicates like==>
Criteria apiUsing the Criteria API to build this dynamic query would be notoriously cumbersome, and Query by Example likely couldn't even handle such a complex query scenario. (codes generate by AI but it looks work) @Repository
public class OrderRepositoryImpl {
@PersistenceContext
private EntityManager entityManager;
public List<Order> findOrdersByCriteria(String creator, Integer createYearAfterAndEqual) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> orderRoot = query.from(Order.class);
List<Predicate> predicates = new ArrayList<>();
// Handle creator condition - OR logic for multiple fields
if (creator != null && !creator.trim().isEmpty()) {
String likePattern = "%" + creator + "%";
// Subquery for createBy.username
Subquery<Long> usernameSubquery = query.subquery(Long.class);
Root<User> userRoot1 = usernameSubquery.from(User.class);
usernameSubquery.select(userRoot1.get("id"))
.where(cb.like(userRoot1.get("username"), likePattern));
// Subquery for createBy.jobNumber
Subquery<Long> jobNumberSubquery = query.subquery(Long.class);
Root<User> userRoot2 = jobNumberSubquery.from(User.class);
jobNumberSubquery.select(userRoot2.get("id"))
.where(cb.like(userRoot2.get("jobNumber"), likePattern));
// Combine all creator-related conditions with OR
Predicate creatorPredicate = cb.or(
cb.in(orderRoot.get("createBy").get("id")).value(usernameSubquery),
cb.in(orderRoot.get("createBy").get("id")).value(jobNumberSubquery),
cb.like(orderRoot.get("creatorNameSnapshot"), likePattern)
);
predicates.add(creatorPredicate);
}
// Handle createYearAfterAndEqual condition
if (createYearAfterAndEqual != null) {
predicates.add(cb.greaterThanOrEqualTo(
orderRoot.get("createYear"), createYearAfterAndEqual));
}
// Apply all predicates with AND logic
if (!predicates.isEmpty()) {
query.where(cb.and(predicates.toArray(new Predicate[0])));
}
return entityManager.createQuery(query).getResultList();
}
} Another advantage is that such query objects are highly intuitive. Any project maintainer can easily understand how the conditions will be applied when this query object is passed in, without needing to examine underlying implementation details like Matchers, nor study the construction process of Criteria or HQL. Simply by declaring how each query parameter's conditions should be used through annotations, the system can operate automatically. This mechanism aligns perfectly with modern frontend-backend separation development patterns. Frontends pass conditions via form submissions, JSON payloads, or URL query parameters. These conditions are then converted into a query object (POJO) in the controller. By parsing the field values and annotations on this query object, we construct the query and automatically generate a Specification. With this capability, when developing form queries with dynamic conditions, we only need to declare the query parameters without writing underlying SQL/HQL/Criteria/QueryDSL code. While this approach may not cover all possible scenarios, it sufficiently meets the majority of requirements. |
Alright, thank you. We have some related functionality in Spring Data REST allowing to customize bindings for Querydsl With your rather specific requirements, I suggest to prototype some functionality outside of Spring Data to see how it goes regarding challenges and assumptions. When trying to match two different classes that are structurally similar, there's no tight contract that would couple both types and the only way to find out that something was renamed or has changed is by running tests. That isn't ideal for Spring Data, where we want to eliminate such class of potential bugs. |
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed. |
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue. |
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:
Then we build the query based on this query object:
Or by mybatis xml
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.
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.
The text was updated successfully, but these errors were encountered: