Skip to content

Commit

Permalink
Hacking.
Browse files Browse the repository at this point in the history
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 Jun 27, 2024
1 parent d1675a5 commit 70c8a65
Show file tree
Hide file tree
Showing 43 changed files with 938 additions and 468 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,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 @@ -173,4 +174,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 IntrospectedQuery query;
private final Lazy<IntrospectedQuery> countQuery;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
Expand All @@ -65,31 +65,20 @@ 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 evaluationContextProvider must not be {@literal null}.
* @param parser must not be {@literal null}.
* @param queryRewriter 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,
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {

super(method, em);

Assert.hasText(queryString, "Query string must not be null or empty");
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
Assert.notNull(parser, "Parser must not be null");
Assert.notNull(queryRewriter, "QueryRewriter must not be null");

this.evaluationContextProvider = evaluationContextProvider;
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
method.isNativeQuery());
this.evaluationContextProvider = queryConfiguration.getEvaluationContextProvider();
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);

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

if (StringUtils.hasText(countQueryString)) {

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

return query.deriveCountQuery(method.getCountQueryProjection());
Expand All @@ -99,8 +88,8 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
return this.createBinder(this.countQuery.get());
});

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

JpaParameters parameters = method.getParameters();
if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {
Expand Down Expand Up @@ -135,7 +124,7 @@ protected ParameterBinder createBinder() {
return createBinder(query);
}

protected ParameterBinder createBinder(DeclaredQuery query) {
protected ParameterBinder createBinder(IntrospectedQuery query) {
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
evaluationContextProvider);
}
Expand All @@ -160,14 +149,14 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
/**
* @return the query
*/
public DeclaredQuery getQuery() {
public IntrospectedQuery getQuery() {
return query;
}

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

Expand Down Expand Up @@ -217,7 +206,7 @@ String applySorting(CachableQuery cachableQuery) {
* Query Sort Rewriter interface.
*/
interface QuerySortRewriter {
String getSorted(DeclaredQuery query, Sort sort);
String getSorted(IntrospectedQuery query, Sort sort);
}

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

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

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

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

if (sort.isUnsorted()) {
return query.getQueryString();
Expand All @@ -264,19 +253,19 @@ public String getSorted(DeclaredQuery query, Sort sort) {
*/
static class CachableQuery {

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

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

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

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

Sort getSort() {
Expand All @@ -285,7 +274,7 @@ Sort getSort() {

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

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2024 the original author or authors.
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,99 +15,42 @@
*/
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
* @since 2.0.3
* @author Mark Paluch
*/
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 jpql(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 nativeQuery(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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;

import org.springframework.util.ObjectUtils;

/**
* @author Mark Paluch
*/
class DefaultDeclaredQuery implements DeclaredQuery {

private final String query;
private final boolean nativeQuery;

DefaultDeclaredQuery(String query, boolean nativeQuery) {
this.query = query;
this.nativeQuery = nativeQuery;
}

public String getQueryString() {
return query;
}

public boolean isNativeQuery() {
return nativeQuery;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof DefaultDeclaredQuery that)) {
return false;
}
if (nativeQuery != that.nativeQuery) {
return false;
}
return ObjectUtils.nullSafeEquals(query, that.query);
}

@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(query);
result = 31 * result + (nativeQuery ? 1 : 0);
return result;
}

@Override
public String toString() {
return (isNativeQuery() ? "[native] " : "[JPQL] ") + getQueryString();
}
}
Loading

0 comments on commit 70c8a65

Please sign in to comment.