-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Introduce API to rewrite JPQL/native query for Sort and PageRequest #2162
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
My current implementation can handle: public interface UserRepository extends Repository<User, Integer> {
@Query(value = "select u.* from User u", nativeQuery = true)
@QueryRewrite(TestQueryRewriter.class)
List<User> findByNativeQuery(String param);
@Query(value = "select u.* from User u")
@QueryRewrite(TestQueryRewriter.class)
List<User> findByNonNativeQuery(String param);
@Query(value = "select u.* from User u")
@QueryRewrite(TestQueryRewriter.class)
List<User> findByNonNativeSortedQuery(String param, Sort sort);
@Query(value = "select u.* from User u")
@QueryRewrite(TestQueryRewriter.class)
List<User> findByNonNativePagedQuery(String param, Pageable pageable);
} static class TestQueryRewriter implements QueryRewriter {
@Override
public String rewrite(String query, Sort sort) {
return query.replaceAll("u", "user");
}
} It supports native/standard, sort params, and pageable params. Because of the way it works, there is no easy way to grab a hold of an existing repository and inject it into the code that generates the SQL statements. So I really don't see a way to do that extension where the repository is ALSO the rewriter. Basically, @Nullable
protected QueryRewriter findQueryRewriter(JpaQueryMethod method) {
Class<? extends QueryRewriter> queryRewriter = method.getQueryRewriter();
if (queryRewriter == null) {
return null;
}
try {
return (QueryRewriter) ((Constructor<?>) queryRewriter.getDeclaredConstructor()).newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
System.out.println(e);
return null;
}
} It does a little reflection (which may be an issue for AOT) to take the classname and instantiate an instance using an empty constructor. |
The general instantiation mechanism should leverage Paging @odrotbohm |
If that rewrite is for manually declared queries only, could the rewriter type just be an additional attribute in the That said, I am a bit hesitant with this one for the following reasons:
I can't quite see a significant advantage in this over implementing a fragment, potentially extracting shared code. Especially if we exposed a flavor of |
You're right, the example is a bit overly complicated,
Adding an attribute to Rewriting queries provides a bit of simplification over a fragment. Assembling a parameterized query for pagination along with a count query requires quite some preparation. Query rewriting would allow folks to specify a token within the query ( Indeed a lot of the tickets above could be solved by providing an explicit count query or by providing a fragment. |
For the record, this is simply an artifact of test method simplification. In production, I'd be VERY resistant to reusing a QueryRewriter, but instead prefer a custom one for every method.
To get the BeanFactory down into the class hierarchy where the QueryRewriter is created seemed a bit invasive. I can tinker with it more.
Maybe I don't follow. I thought the purpose of this proposal was to offer a last-minute escape where the user can tweak the finalized query at the last minute. So instead of adding more fragments and "things", we could instead do all the parsing needed, and then use a QueryRewriter right before the SQL goes to the entityManager. |
RepositoryFactorySupport implementations may need access to the underlying BeanFactory. Add a protected getter to access it. Related: spring-projects/spring-data-jpa#2162
Also ensure it works for external QueryRewriters as well as repository-based QueryRewriters. See #2162.
If you have any final comments about which way you'd prefer things, I can trim out the stuff we don't want and proceed to merge. |
The plan, going forward, is to drop We must also extend support for CDI. |
Some implementations (Spring Data JPA especially) need access to the underlying BeanFactory. Add a protected getter. Related: spring-projects/spring-data-jpa#2162 Closes #2614
Some implementations (Spring Data JPA especially) need access to the underlying BeanFactory. Add a protected getter. Related: spring-projects/spring-data-jpa#2162 Closes #2614
Allow a QueryRewriter to be applied to any query crafted using @query via an additional @QueryRewriter annotation. Also supported directly inside @query. Depends on: spring-projects/spring-data-commons#2614 See #2162.
Depends on: spring-projects/spring-data-commons#2614 |
spring-projects/spring-data-commons#2614 is deemed unnecessary. Overriding |
Eagerly resolve QueryRewriter instances when creating JPA query objects. Tweak documentation wording. Tweak type names to align with naming scheme. See #2162.
Right now, we face several tickets where we run into issues when trying to rewrite a query to append
ORDER BY
fields and pagination.The initial idea of providing a query parser introduces a lot of complexity to parse JPQL queries and more complexity when parsing native SQL queries as we are not in full control of each dialect nor it makes sense to have a full parser that considers the complete SQL specification.
To address the mentioned issues (see #2079, #2045, #1895, and others), we should investigate whether we can provide a callback API where the calling code can handle the actual rewrite. Calling a
@Query
annotated method accepting eitherSort
orPageable
arguments, we would activate the query rewrite callback for rewriting the query. The code path utilizingQueryUtils
would be entirely skipped so that the rewrite responsibility is within the calling code.As starting point, we could have a
@QueryRewrite
annotation accepting a Java class that implements the callback API, something along the lines of:to be used as:
Following that idea, one could also write:
where
QueryRewrite.Repository.class
can be a marker to indicate that the actual repository interface contains the actual method implementation.The text was updated successfully, but these errors were encountered: