Skip to content

Commit d6d84ec

Browse files
DiegoKrupitzagregturn
authored andcommitted
Create @QueryEnhancerOverride to let developes override the automatically selected QueryEnhancer.
Sometimes queryies contain content that cannot be pasrsed by special implementations such as `JSqlParserQueryEnhancer` (or other potential solutions). We implemented a means for developers to deliberately override the automatic choice with their own via annotation. Resolves #2564.
1 parent 0846027 commit d6d84ec

19 files changed

+662
-87
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java

+3-6
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.Query;
2020

21-
import org.apache.commons.logging.Log;
22-
import org.apache.commons.logging.LogFactory;
23-
2421
import org.springframework.data.domain.Pageable;
2522
import org.springframework.data.domain.Sort;
2623
import org.springframework.data.jpa.repository.QueryRewriter;
@@ -65,8 +62,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6562
* @param queryRewriter must not be {@literal null}.
6663
*/
6764
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
68-
@Nullable String countQueryString, QueryRewriter queryRewriter, QueryMethodEvaluationContextProvider evaluationContextProvider,
69-
SpelExpressionParser parser) {
65+
@Nullable String countQueryString, QueryRewriter queryRewriter,
66+
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
7067

7168
super(method, em);
7269

@@ -77,7 +74,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
7774

7875
this.evaluationContextProvider = evaluationContextProvider;
7976
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
80-
method.isNativeQuery());
77+
method.isNativeQuery(), method.getQueryEnhancerOverride());
8178

8279
DeclaredQuery countQuery = query.deriveCountQuery(countQueryString, method.getCountQueryProjection());
8380
this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser,

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java

+27-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* @author Diego Krupitza
2828
* @since 2.0.3
2929
*/
30-
interface DeclaredQuery {
30+
public interface DeclaredQuery {
3131

3232
/**
3333
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
@@ -105,10 +105,35 @@ default boolean usesPaging() {
105105

106106
/**
107107
* Return whether the query is a native query of not.
108-
*
108+
*
109109
* @return <code>true</code> if native query otherwise <code>false</code>
110110
*/
111111
default boolean isNativeQuery() {
112112
return false;
113113
}
114+
115+
/**
116+
* Gets the {@link QueryEnhancer} used for this Query.
117+
*
118+
* @return the concrete {@link QueryEnhancer} implementation used for this given Query
119+
*/
120+
@Nullable
121+
QueryEnhancer getQueryEnhancer();
122+
123+
/**
124+
* Returns the method's {@link QueryEnhancerOverride} annotation
125+
*
126+
* @return
127+
*/
128+
@Nullable
129+
QueryEnhancerOverride getQueryEnhancerOverride();
130+
131+
/**
132+
* Returns whether the method has a {@link QueryEnhancerOverride} or not
133+
*
134+
* @return
135+
*/
136+
default boolean hasQueryEnhancerOverride() {
137+
return getQueryEnhancerOverride() != null;
138+
}
114139
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java

+7-13
Original file line numberDiff line numberDiff line change
@@ -26,41 +26,35 @@
2626
* @author Diego Krupitza
2727
* @since 2.7.0
2828
*/
29-
public class DefaultQueryEnhancer implements QueryEnhancer {
30-
31-
private final DeclaredQuery query;
29+
public class DefaultQueryEnhancer extends QueryEnhancer {
3230

3331
public DefaultQueryEnhancer(DeclaredQuery query) {
34-
this.query = query;
32+
super(query);
3533
}
3634

3735
@Override
3836
public String applySorting(Sort sort, @Nullable String alias) {
39-
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
37+
return QueryUtils.applySorting(getQuery().getQueryString(), sort, alias);
4038
}
4139

4240
@Override
4341
public String detectAlias() {
44-
return QueryUtils.detectAlias(this.query.getQueryString());
42+
return QueryUtils.detectAlias(getQuery().getQueryString());
4543
}
4644

4745
@Override
4846
public String createCountQueryFor(@Nullable String countProjection) {
49-
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
47+
return QueryUtils.createCountQueryFor(getQuery().getQueryString(), countProjection);
5048
}
5149

5250
@Override
5351
public String getProjection() {
54-
return QueryUtils.getProjection(this.query.getQueryString());
52+
return QueryUtils.getProjection(getQuery().getQueryString());
5553
}
5654

5755
@Override
5856
public Set<String> getJoinAliases() {
59-
return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
57+
return QueryUtils.getOuterJoinAliases(getQuery().getQueryString());
6058
}
6159

62-
@Override
63-
public DeclaredQuery getQuery() {
64-
return this.query;
65-
}
6660
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java

+11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
* NULL-Object pattern implementation for {@link DeclaredQuery}.
2626
*
2727
* @author Jens Schauder
28+
* @author Diego Krupitza
2829
* @since 2.0.3
2930
*/
3031
class EmptyDeclaredQuery implements DeclaredQuery {
@@ -76,4 +77,14 @@ public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable Str
7677
public boolean usesJdbcStyleParameters() {
7778
return false;
7879
}
80+
81+
@Override
82+
public QueryEnhancer getQueryEnhancer() {
83+
return null;
84+
}
85+
86+
@Override
87+
public QueryEnhancerOverride getQueryEnhancerOverride() {
88+
return null;
89+
}
7990
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
import org.springframework.expression.ParserContext;
2323
import org.springframework.expression.spel.standard.SpelExpressionParser;
2424
import org.springframework.expression.spel.support.StandardEvaluationContext;
25+
import org.springframework.lang.Nullable;
2526
import org.springframework.util.Assert;
2627

2728
/**
2829
* Extension of {@link StringQuery} that evaluates the given query string as a SpEL template-expression.
2930
* <p>
30-
* Currently the following template variables are available:
31+
* Currently, the following template variables are available:
3132
* <ol>
3233
* <li>{@code #entityName} - the simple class name of the given entity</li>
3334
* <ol>
@@ -60,7 +61,23 @@ class ExpressionBasedStringQuery extends StringQuery {
6061
*/
6162
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
6263
boolean nativeQuery) {
63-
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
64+
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query),
65+
null);
66+
}
67+
68+
/**
69+
* Creates a new {@link ExpressionBasedStringQuery} for the given query and {@link EntityMetadata}.
70+
*
71+
* @param query must not be {@literal null} or empty.
72+
* @param metadata must not be {@literal null}.
73+
* @param parser must not be {@literal null}.
74+
* @param nativeQuery is a given query is native or not
75+
* @param queryEnhancerOverride may be {@literal null}.
76+
*/
77+
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
78+
boolean nativeQuery, @Nullable QueryEnhancerOverride queryEnhancerOverride) {
79+
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query),
80+
queryEnhancerOverride);
6481
}
6582

6683
/**
@@ -74,7 +91,8 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, S
7491
*/
7592
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
7693
SpelExpressionParser parser, boolean nativeQuery) {
77-
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
94+
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery,
95+
query.getQueryEnhancerOverride());
7896
}
7997

8098
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java

+29-25
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlCount;
19-
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlLower;
20-
import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression;
18+
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
19+
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
2120

2221
import net.sf.jsqlparser.JSQLParserException;
2322
import net.sf.jsqlparser.expression.Alias;
@@ -29,11 +28,23 @@
2928
import net.sf.jsqlparser.statement.delete.Delete;
3029
import net.sf.jsqlparser.statement.insert.Insert;
3130
import net.sf.jsqlparser.statement.merge.Merge;
32-
import net.sf.jsqlparser.statement.select.*;
31+
import net.sf.jsqlparser.statement.select.OrderByElement;
32+
import net.sf.jsqlparser.statement.select.PlainSelect;
33+
import net.sf.jsqlparser.statement.select.Select;
34+
import net.sf.jsqlparser.statement.select.SelectBody;
35+
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
36+
import net.sf.jsqlparser.statement.select.SelectItem;
37+
import net.sf.jsqlparser.statement.select.SetOperationList;
38+
import net.sf.jsqlparser.statement.select.WithItem;
3339
import net.sf.jsqlparser.statement.update.Update;
3440
import net.sf.jsqlparser.statement.values.ValuesStatement;
3541

36-
import java.util.*;
42+
import java.util.ArrayList;
43+
import java.util.Collections;
44+
import java.util.HashSet;
45+
import java.util.List;
46+
import java.util.Objects;
47+
import java.util.Set;
3748
import java.util.stream.Collectors;
3849

3950
import org.springframework.data.domain.Sort;
@@ -50,17 +61,15 @@
5061
* @author Geoffrey Deremetz
5162
* @since 2.7.0
5263
*/
53-
public class JSqlParserQueryEnhancer implements QueryEnhancer {
64+
public class JSqlParserQueryEnhancer extends QueryEnhancer {
5465

55-
private final DeclaredQuery query;
5666
private final ParsedType parsedType;
5767

5868
/**
5969
* @param query the query we want to enhance. Must not be {@literal null}.
6070
*/
6171
public JSqlParserQueryEnhancer(DeclaredQuery query) {
62-
63-
this.query = query;
72+
super(query);
6473
this.parsedType = detectParsedType();
6574
}
6675

@@ -72,7 +81,7 @@ public JSqlParserQueryEnhancer(DeclaredQuery query) {
7281
private ParsedType detectParsedType() {
7382

7483
try {
75-
Statement statement = CCJSqlParserUtil.parse(this.query.getQueryString());
84+
Statement statement = CCJSqlParserUtil.parse(this.getQuery().getQueryString());
7685

7786
if (statement instanceof Insert) {
7887
return ParsedType.INSERT;
@@ -95,7 +104,7 @@ private ParsedType detectParsedType() {
95104
@Override
96105
public String applySorting(Sort sort, @Nullable String alias) {
97106

98-
String queryString = query.getQueryString();
107+
String queryString = this.getQuery().getQueryString();
99108
Assert.hasText(queryString, "Query must not be null or empty");
100109

101110
if (this.parsedType != ParsedType.SELECT) {
@@ -192,7 +201,7 @@ Set<String> getSelectionAliases() {
192201
return new HashSet<>();
193202
}
194203

195-
Select selectStatement = parseSelectStatement(this.query.getQueryString());
204+
Select selectStatement = parseSelectStatement(this.getQuery().getQueryString());
196205
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
197206
return this.getSelectionAliases(selectBody);
198207
}
@@ -280,7 +289,7 @@ private OrderByElement getOrderClause(final Set<String> joinAliases, final Set<S
280289

281290
@Override
282291
public String detectAlias() {
283-
return detectAlias(this.query.getQueryString());
292+
return detectAlias(this.getQuery().getQueryString());
284293
}
285294

286295
/**
@@ -354,18 +363,18 @@ private String detectAlias(Merge mergeStatement) {
354363
public String createCountQueryFor(@Nullable String countProjection) {
355364

356365
if (this.parsedType != ParsedType.SELECT) {
357-
return this.query.getQueryString();
366+
return this.getQuery().getQueryString();
358367
}
359368

360-
Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty");
369+
Assert.hasText(this.getQuery().getQueryString(), "OriginalQuery must not be null or empty");
361370

362-
Select selectStatement = parseSelectStatement(this.query.getQueryString());
371+
Select selectStatement = parseSelectStatement(this.getQuery().getQueryString());
363372

364373
/*
365374
We only support count queries for {@link PlainSelect}.
366375
*/
367376
if (!(selectStatement.getSelectBody() instanceof PlainSelect)) {
368-
return this.query.getQueryString();
377+
return this.getQuery().getQueryString();
369378
}
370379

371380
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
@@ -423,9 +432,9 @@ public String getProjection() {
423432
return "";
424433
}
425434

426-
Assert.hasText(query.getQueryString(), "Query must not be null or empty");
435+
Assert.hasText(this.getQuery().getQueryString(), "Query must not be null or empty");
427436

428-
Select selectStatement = parseSelectStatement(query.getQueryString());
437+
Select selectStatement = parseSelectStatement(this.getQuery().getQueryString());
429438

430439
if (selectStatement.getSelectBody() instanceof ValuesStatement) {
431440
return "";
@@ -451,7 +460,7 @@ public String getProjection() {
451460

452461
@Override
453462
public Set<String> getJoinAliases() {
454-
return this.getJoinAliases(this.query.getQueryString());
463+
return this.getJoinAliases(this.getQuery().getQueryString());
455464
}
456465

457466
/**
@@ -492,11 +501,6 @@ private boolean onlyASingleColumnProjection(List<SelectItem> projection) {
492501
&& (((SelectExpressionItem) projection.get(0)).getExpression()) instanceof Column;
493502
}
494503

495-
@Override
496-
public DeclaredQuery getQuery() {
497-
return this.query;
498-
}
499-
500504
/**
501505
* An enum to represent the top level parsed statement of the provided query.
502506
* <ul>

0 commit comments

Comments
 (0)