Skip to content

Commit

Permalink
Introduce QueryEnhancerSelector to configure which `QueryEnhancerFa…
Browse files Browse the repository at this point in the history
…ctory` to use.

Introduce QueryEnhancerSelector to EnableJpaRepositories.

Also, split DeclaredQuery into two interfaces to resolve the inner cycle of query introspection while just a value object is being created.

Introduce JpaQueryConfiguration to capture a multitude of configuration elements.
  • Loading branch information
mp911de committed Jan 22, 2025
1 parent 06dd7e8 commit 886bfaa
Show file tree
Hide file tree
Showing 49 changed files with 1,064 additions and 694 deletions.
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 StringQuery query;
private final Lazy<IntrospectedQuery> countQuery;
private final ValueExpressionDelegate valueExpressionDelegate;
private final QueryRewriter queryRewriter;
private final QuerySortRewriter querySortRewriter;
Expand All @@ -64,37 +64,32 @@ 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(() -> this.createBinder(this.countQuery.get()));

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

JpaParameters parameters = method.getParameters();

Expand All @@ -108,7 +103,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
}
}

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

Expand All @@ -135,7 +130,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 @@ -159,14 +154,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 @@ -207,16 +202,15 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
}

String applySorting(CachableQuery cachableQuery) {

return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
}

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

/**
Expand All @@ -226,25 +220,24 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {

INSTANCE;

public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {

return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
}
}

static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {

private volatile String cachedQueryString;

public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {

if (sort.isSorted()) {
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
}

String cachedQueryString = this.cachedQueryString;
if (cachedQueryString == null) {
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
}

Expand All @@ -263,7 +256,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter {
private volatile String cachedQueryString;

@Override
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {

if (sort.isUnsorted()) {

Expand All @@ -288,21 +281,21 @@ public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedTyp
*/
static class CachableQuery {

private final DeclaredQuery declaredQuery;
private final StringQuery query;
private final String queryString;
private final Sort sort;
private final ReturnedType returnedType;

CachableQuery(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {

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

DeclaredQuery getDeclaredQuery() {
return declaredQuery;
StringQuery getDeclaredQuery() {
return query;
}

Sort getSort() {
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
* @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

0 comments on commit 886bfaa

Please sign in to comment.