Skip to content
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

Introduce QueryEnhancerSelector to configure which QueryEnhancerFactory to use #3527

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data JPA Parent</name>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-envers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.5.0-SNAPSHOT</version>
<version>3.5.0-GH-2989-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ OR TREAT(p AS SmallProject).name LIKE 'Persist%'
OR p.description LIKE "cost overrun"
""";

query = DeclaredQuery.of(s, false);
enhancer = QueryEnhancerFactory.forQuery(query);
query = DeclaredQuery.ofJpql(s);
enhancer = QueryEnhancerFactory.forQuery(query).create(query);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void doSetup() throws IOException {
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";

enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.of(s, true));
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.ofNative(s));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
Expand Down Expand Up @@ -181,4 +182,11 @@
* @return a single character used for escaping.
*/
char escapeCharacter() default '\\';

/**
* Configures the {@link QueryEnhancerSelector} to select a query enhancer for query introspection and transformation.
*
* @return a {@link QueryEnhancerSelector} class providing a no-args constructor.
*/
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
builder.addPropertyReference("entityManager", entityManagerRefs.get(source));
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);

if (source instanceof AnnotationRepositoryConfigurationSource) {
builder.addPropertyValue("queryEnhancerSelector",
source.getAttribute("queryEnhancerSelector", Class.class).orElse(null));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
*/
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {

private final DeclaredQuery query;
private final Lazy<DeclaredQuery> countQuery;
private final EntityQuery query;
private final Lazy<IntrospectedQuery> countQuery;
private final ValueExpressionDelegate valueExpressionDelegate;
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
private final QueryRewriter queryRewriter;
Expand All @@ -65,40 +65,34 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
* @param em must not be {@literal null}.
* @param queryString must not be {@literal null}.
* @param countQueryString must not be {@literal null}.
* @param queryRewriter must not be {@literal null}.
* @param valueExpressionDelegate must not be {@literal null}.
* @param queryConfiguration must not be {@literal null}.
*/
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
@Nullable String countQueryString, QueryRewriter queryRewriter,
ValueExpressionDelegate valueExpressionDelegate) {
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {

super(method, em);

Assert.hasText(queryString, "Query string must not be null or empty");
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");

this.valueExpressionDelegate = valueExpressionDelegate;
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
method.isNativeQuery());
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);

this.countQuery = Lazy.of(() -> {

if (StringUtils.hasText(countQueryString)) {

return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
method.isNativeQuery());
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
}

return query.deriveCountQuery(method.getCountQueryProjection());
return this.query.deriveCountQuery(method.getCountQueryProjection());
});

this.countParameterBinder = Lazy.of(() -> {
return this.createBinder(this.countQuery.get());
});

this.queryRewriter = queryRewriter;
this.queryRewriter = queryConfiguration.getQueryRewriter(method);

JpaParameters parameters = method.getParameters();
if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {
Expand All @@ -107,7 +101,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
this.querySortRewriter = NoOpQuerySortRewriter.INSTANCE;
}

Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
Assert.isTrue(method.isNativeQuery() || !this.query.usesJdbcStyleParameters(),
"JDBC style parameters (?) are not supported for JPA queries");
}

Expand Down Expand Up @@ -137,7 +131,7 @@ protected ParameterBinder createBinder() {
return createBinder(query);
}

protected ParameterBinder createBinder(DeclaredQuery query) {
protected ParameterBinder createBinder(IntrospectedQuery query) {
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
valueExpressionDelegate, valueExpressionContextProvider);
}
Expand All @@ -162,14 +156,14 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
/**
* @return the query
*/
public DeclaredQuery getQuery() {
public EntityQuery getQuery() {
return query;
}

/**
* @return the countQuery
*/
public DeclaredQuery getCountQuery() {
public IntrospectedQuery getCountQuery() {
return countQuery.get();
}

Expand Down Expand Up @@ -210,16 +204,14 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
}

String applySorting(CachableQuery cachableQuery) {

return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery()).applySorting(cachableQuery.getSort(),
cachableQuery.getAlias());
return cachableQuery.getDeclaredQuery().applySorting(cachableQuery.getSort());
}

/**
* Query Sort Rewriter interface.
*/
interface QuerySortRewriter {
String getSorted(DeclaredQuery query, Sort sort);
String getSorted(EntityQuery query, Sort sort);
}

/**
Expand All @@ -228,7 +220,7 @@ interface QuerySortRewriter {
enum NoOpQuerySortRewriter implements QuerySortRewriter {
INSTANCE;

public String getSorted(DeclaredQuery query, Sort sort) {
public String getSorted(EntityQuery query, Sort sort) {

if (sort.isSorted()) {
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
Expand All @@ -247,7 +239,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter {
AbstractStringBasedJpaQuery.this::applySorting);

@Override
public String getSorted(DeclaredQuery query, Sort sort) {
public String getSorted(EntityQuery query, Sort sort) {

if (sort.isUnsorted()) {
return query.getQueryString();
Expand All @@ -266,30 +258,25 @@ public String getSorted(DeclaredQuery query, Sort sort) {
*/
static class CachableQuery {

private final DeclaredQuery declaredQuery;
private final EntityQuery introspectedQuery;
private final String queryString;
private final Sort sort;

CachableQuery(DeclaredQuery query, Sort sort) {
CachableQuery(EntityQuery query, Sort sort) {

this.declaredQuery = query;
this.introspectedQuery = query;
this.queryString = query.getQueryString();
this.sort = sort;
}

DeclaredQuery getDeclaredQuery() {
return declaredQuery;
EntityQuery getDeclaredQuery() {
return introspectedQuery;
}

Sort getSort() {
return sort;
}

@Nullable
String getAlias() {
return declaredQuery.getAlias();
}

@Override
public boolean equals(Object o) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,99 +15,45 @@
*/
package org.springframework.data.jpa.repository.query;

import java.util.List;

import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;

/**
* A wrapper for a String representation of a query offering information about the query.
* Interface defining the contract to represent a declared query.
*
* @author Jens Schauder
* @author Diego Krupitza
* @author Mark Paluch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should leave the authors intact, even when the class changes completely.

* @since 2.0.3
*/
interface DeclaredQuery {
public interface DeclaredQuery {

/**
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
* Creates a DeclaredQuery for a JPQL query.
*
* @param query might be {@literal null} or empty.
* @param nativeQuery is a given query is native or not
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
* @param query the JPQL query string.
* @return
*/
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
static DeclaredQuery ofJpql(String query) {
return new DefaultDeclaredQuery(query, false);
}

/**
* @return whether the underlying query has at least one named parameter.
*/
boolean hasNamedParameter();

/**
* Returns the query string.
*/
String getQueryString();

/**
* Returns the main alias used in the query.
*
* @return the alias
*/
@Nullable
String getAlias();

/**
* Returns whether the query is using a constructor expression.
* Creates a DeclaredQuery for a native query.
*
* @since 1.10
* @param query the native query string.
* @return
*/
boolean hasConstructorExpression();

/**
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
*/
boolean isDefaultProjection();

/**
* Returns the {@link ParameterBinding}s registered.
*/
List<ParameterBinding> getParameterBindings();

/**
* Creates a new {@literal DeclaredQuery} representing a count query, i.e. a query returning the number of rows to be
* expected from the original query, either derived from the query wrapped by this instance or from the information
* passed as arguments.
*
* @param countQueryProjection an optional return type for the query.
* @return a new {@literal DeclaredQuery} instance.
*/
DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection);

/**
* @return whether paging is implemented in the query itself, e.g. using SpEL expressions.
* @since 2.0.6
*/
default boolean usesPaging() {
return false;
static DeclaredQuery ofNative(String query) {
return new DefaultDeclaredQuery(query, true);
}

/**
* Returns whether the query uses JDBC style parameters, i.e. parameters denoted by a simple ? without any index or
* name.
*
* @return Whether the query uses JDBC style parameters.
* @since 2.0.6
* Returns the query string.
*/
boolean usesJdbcStyleParameters();
String getQueryString();

/**
* Return whether the query is a native query of not.
*
* @return <code>true</code> if native query otherwise <code>false</code>
*/
default boolean isNativeQuery() {
return false;
}
boolean isNativeQuery();
}
Loading