Skip to content

Commit 4a3842b

Browse files
committed
Introduce flag to skip JSqlParser for a given custom query.
See #2989
1 parent 729acf6 commit 4a3842b

File tree

10 files changed

+65
-5
lines changed

10 files changed

+65
-5
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,12 @@
8686
* @since 3.0
8787
*/
8888
Class<? extends QueryRewriter> queryRewriter() default QueryRewriter.IdentityQueryRewriter.class;
89+
90+
/**
91+
* For native queries, indicate whether or not to skip the JSqlParser.
92+
*
93+
* @return
94+
* @since 3.2
95+
*/
96+
boolean skipJSql() default false;
8997
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
7575

7676
this.evaluationContextProvider = evaluationContextProvider;
7777
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
78-
method.isNativeQuery());
78+
method.isNativeQuery(), method.skipJSql());
7979

8080
this.countQuery = Lazy.of(() -> {
8181
DeclaredQuery countQuery = query.deriveCountQuery(countQueryString, method.getCountQueryProjection());

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

+4
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,8 @@ default boolean usesPaging() {
111111
default boolean isNativeQuery() {
112112
return false;
113113
}
114+
115+
default boolean skipJSql() {
116+
return false;
117+
}
114118
}

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,15 @@ class ExpressionBasedStringQuery extends StringQuery {
5959
* @param parser must not be {@literal null}.
6060
* @param nativeQuery is a given query is native or not
6161
*/
62+
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
63+
boolean nativeQuery, boolean skipJSql) {
64+
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query),
65+
skipJSql);
66+
}
67+
6268
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
6369
boolean nativeQuery) {
64-
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
70+
this(query, metadata, parser, nativeQuery, false);
6571
}
6672

6773
/**
@@ -75,7 +81,7 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, S
7581
*/
7682
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
7783
SpelExpressionParser parser, boolean nativeQuery) {
78-
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
84+
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery, query.skipJSql());
7985
}
8086

8187
/**

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

+6
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public class JpaQueryMethod extends QueryMethod {
9494
private final Lazy<JpaEntityGraph> jpaEntityGraph;
9595
private final Lazy<Modifying> modifying;
9696
private final Lazy<Boolean> isNativeQuery;
97+
private final Lazy<Boolean> skipJSql;
9798
private final Lazy<Boolean> isCollectionQuery;
9899
private final Lazy<Boolean> isProcedureQuery;
99100
private final Lazy<JpaEntityMetadata<?>> entityMetadata;
@@ -136,6 +137,7 @@ protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionF
136137
return new JpaEntityGraph(entityGraph, getNamedQueryName());
137138
});
138139
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
140+
this.skipJSql = Lazy.of(() -> getAnnotationValue("skipJSql", Boolean.class));
139141
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
140142
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
141143
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
@@ -387,6 +389,10 @@ boolean isNativeQuery() {
387389
return this.isNativeQuery.get();
388390
}
389391

392+
boolean skipJSql() {
393+
return this.skipJSql.get();
394+
}
395+
390396
@Override
391397
public String getNamedQueryName() {
392398

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public static QueryEnhancer forQuery(DeclaredQuery query) {
6060

6161
if (query.isNativeQuery()) {
6262

63-
if (jSqlParserPresent) {
63+
if (jSqlParserPresent && !query.skipJSql()) {
6464
/*
6565
* If JSqlParser fails, throw some alert signaling that people should write a custom Impl.
6666
*/

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class StringQuery implements DeclaredQuery {
6565
private final boolean containsPageableInSpel;
6666
private final boolean usesJdbcStyleParameters;
6767
private final boolean isNative;
68+
private final boolean skipJSql;
6869
private final QueryEnhancer queryEnhancer;
6970

7071
/**
@@ -73,11 +74,12 @@ class StringQuery implements DeclaredQuery {
7374
* @param query must not be {@literal null} or empty.
7475
*/
7576
@SuppressWarnings("deprecation")
76-
StringQuery(String query, boolean isNative) {
77+
StringQuery(String query, boolean isNative, boolean skipJSql) {
7778

7879
Assert.hasText(query, "Query must not be null or empty");
7980

8081
this.isNative = isNative;
82+
this.skipJSql = skipJSql;
8183
this.bindings = new ArrayList<>();
8284
this.containsPageableInSpel = query.contains("#pageable");
8385

@@ -92,6 +94,10 @@ class StringQuery implements DeclaredQuery {
9294
this.hasConstructorExpression = this.queryEnhancer.hasConstructorExpression();
9395
}
9496

97+
StringQuery(String query, boolean isNative) {
98+
this(query, isNative, false);
99+
}
100+
95101
/**
96102
* Returns whether we have found some like bindings.
97103
*/
@@ -157,6 +163,11 @@ public boolean isNativeQuery() {
157163
return isNative;
158164
}
159165

166+
@Override
167+
public boolean skipJSql() {
168+
return skipJSql;
169+
}
170+
160171
/**
161172
* A parser that extracts the parameter bindings from a given query string.
162173
*

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+13
Original file line numberDiff line numberDiff line change
@@ -2914,6 +2914,19 @@ void supportsProjectionsWithNativeQueries() {
29142914
assertThat(result.getLastname()).isEqualTo(user.getLastname());
29152915
}
29162916

2917+
@Test // GH-2989
2918+
void supportsProjectionsWithNativeQueriesSkippingJSql() {
2919+
2920+
flushTestUsers();
2921+
2922+
User user = repository.findAll().get(0);
2923+
2924+
NameOnly result = repository.findByNativeQueryWithNoJSql(user.getId());
2925+
2926+
assertThat(result.getFirstname()).isEqualTo(user.getFirstname());
2927+
assertThat(result.getLastname()).isEqualTo(user.getLastname());
2928+
}
2929+
29172930
@Test // DATAJPA-1248
29182931
void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() {
29192932

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java

+7
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,13 @@ void modifyingQueriesAreDetectedCorrectly() {
625625
assertThat(QueryEnhancerFactory.forQuery(modiQuery).createCountQueryFor()).isEqualToIgnoringCase(modifyingQuery);
626626
}
627627

628+
@Test // GH-2989
629+
void skippingJSqlShouldRevertToDefaultQueryEnhancer() {
630+
631+
assertThat(getEnhancer(new StringQuery(QUERY, true, false))).isInstanceOf(JSqlParserQueryEnhancer.class);
632+
assertThat(getEnhancer(new StringQuery(QUERY, true, true))).isInstanceOf(DefaultQueryEnhancer.class);
633+
}
634+
628635
@ParameterizedTest // GH-2593
629636
@MethodSource("insertStatementIsProcessedSameAsDefaultSource")
630637
void insertStatementIsProcessedSameAsDefault(String insertQuery) {

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java

+5
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
585585
@Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
586586
NameOnly findByNativeQuery(Integer id);
587587

588+
// GH-2989
589+
@Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true, skipJSql = true)
590+
NameOnly findByNativeQueryWithNoJSql(Integer id);
591+
592+
588593
// DATAJPA-1248
589594
@Query(value = "SELECT emailaddress FROM SD_User WHERE id = ?1", nativeQuery = true)
590595
EmailOnly findEmailOnlyByNativeQuery(Integer id);

0 commit comments

Comments
 (0)