Skip to content

Commit b8e3671

Browse files
committed
Added fallback mechanism for QueryEnhancer.
In spring-projects#2564 we discovered that sometimes queries contain content that cannot be parsed by special implementation such as `JSqlParserQueryEnhancer`. Therefore, we implement a fallback mechanism that tries to use the first possible suitable implementation for a given query, that does not fail with an exception on creation. If the first one fails we fallback to the next one until we have reached the `DefaultQueryEnhancer`. Closes spring-projects#2564
1 parent 91ba4e2 commit b8e3671

File tree

5 files changed

+86
-13
lines changed

5 files changed

+86
-13
lines changed

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

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

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
1821
import org.apache.commons.logging.Log;
1922
import org.apache.commons.logging.LogFactory;
23+
import org.springframework.data.util.Lazy;
2024

2125
/**
2226
* Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link DeclaredQuery}.
@@ -33,19 +37,63 @@ public final class QueryEnhancerFactory {
3337

3438
private QueryEnhancerFactory() {}
3539

40+
/**
41+
* Creates a {@link List} containing all the possible {@link QueryEnhancer} implementations for native queries. The
42+
* order of the entries indicates that the first {@link QueryEnhancer} should be used. If this is not possible the
43+
* next until the last one is reached which will be always {@link DefaultQueryEnhancer}.
44+
*
45+
* @param query the query for which the list should be created for
46+
* @return list containing all the suitable implementations for the given query
47+
*/
48+
private static List<Lazy<QueryEnhancer>> nativeQueryEnhancers(DeclaredQuery query) {
49+
ArrayList<Lazy<QueryEnhancer>> suitableImplementations = new ArrayList<>();
50+
51+
if (qualifiesForJSqlParserUsage(query)) {
52+
suitableImplementations.add(Lazy.of(() -> new JSqlParserQueryEnhancer(query)));
53+
}
54+
55+
// DefaultQueryEnhancer has to be the last since this is our fallback
56+
suitableImplementations.add(Lazy.of(() -> new DefaultQueryEnhancer(query)));
57+
return suitableImplementations;
58+
}
59+
60+
/**
61+
* Creates a {@link List} containing all the possible {@link QueryEnhancer} implementations for non-native queries.
62+
* The order of the entries indicates that the first {@link QueryEnhancer} should be used. If this is not possible the
63+
* next until the last one is reached which will be always {@link DefaultQueryEnhancer}.
64+
*
65+
* @param query the query for which the list should be created for
66+
* @return list containing all the suitable implementations for the given query
67+
*/
68+
private static List<Lazy<QueryEnhancer>> nonNativeQueryEnhancers(DeclaredQuery query) {
69+
ArrayList<Lazy<QueryEnhancer>> suitableImplementations = new ArrayList<>();
70+
71+
// DefaultQueryEnhancer has to be the last since this is our fallback
72+
suitableImplementations.add(Lazy.of(() -> new DefaultQueryEnhancer(query)));
73+
return suitableImplementations;
74+
}
75+
3676
/**
3777
* Creates a new {@link QueryEnhancer} for the given {@link DeclaredQuery}.
3878
*
3979
* @param query must not be {@literal null}.
4080
* @return an implementation of {@link QueryEnhancer} that suits the query the most
4181
*/
4282
public static QueryEnhancer forQuery(DeclaredQuery query) {
83+
List<Lazy<QueryEnhancer>> suitableQueryEnhancers = query.isNativeQuery() ? nativeQueryEnhancers(query)
84+
: nonNativeQueryEnhancers(query);
4385

44-
if (qualifiesForJSqlParserUsage(query)) {
45-
return new JSqlParserQueryEnhancer(query);
46-
} else {
47-
return new DefaultQueryEnhancer(query);
86+
for (Lazy<QueryEnhancer> suitableQueryEnhancer : suitableQueryEnhancers) {
87+
try {
88+
return suitableQueryEnhancer.get();
89+
} catch (Exception e) {
90+
LOG.debug("Falling back to next QueryEnhancer implementation, due to exception.", e);
91+
}
4892
}
93+
94+
throw new IllegalStateException(
95+
"No QueryEnhancer found for the query [%s]! This should not happen since the default implementation (DefaultQueryEnhancer) should have been called!"
96+
.formatted(query.getQueryString()));
4997
}
5098

5199
/**

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -2969,7 +2969,7 @@ void complexWithNativeStatement() {
29692969
}
29702970

29712971
@Test // GH-2607
2972-
void containsWithCollection(){
2972+
void containsWithCollection() {
29732973

29742974
firstUser.getAttributes().add("cool");
29752975
firstUser.getAttributes().add("hip");
@@ -2986,6 +2986,25 @@ void containsWithCollection(){
29862986
assertThat(result).containsOnly(firstUser, secondUser);
29872987
}
29882988

2989+
@Test
2990+
void nativeQueryWithSpELStatementTest() {
2991+
2992+
String exampleFirstName = "Peter";
2993+
User firstPeter = new User(exampleFirstName, "2", "[email protected]");
2994+
repository.save(firstPeter);
2995+
2996+
User secondPeter = new User(exampleFirstName, "2", "[email protected]");
2997+
repository.save(secondPeter);
2998+
2999+
User firstDiego = new User("Diego", "2", "[email protected]");
3000+
repository.save(secondPeter);
3001+
3002+
User exampleUser = new User(exampleFirstName, "IGNORE", "[email protected]");
3003+
List<User> foundData = repository.nativeQueryWithSpELStatement(exampleUser);
3004+
3005+
assertThat(foundData).hasSize(2);
3006+
}
3007+
29893008
private Page<User> executeSpecWithSort(Sort sort) {
29903009

29913010
flushTestUsers();

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

+11
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,15 @@ void createsJSqlImplementationForNativeQuery() {
4747
assertThat(queryEnhancer) //
4848
.isInstanceOf(JSqlParserQueryEnhancer.class);
4949
}
50+
51+
@Test
52+
void fallsBackToOtherQueryEnhancerWhenUsingHibernatePlaceHolder() {
53+
StringQuery query = new StringQuery("SELECT c.* FROM {h-schema}countries c", true);
54+
55+
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
56+
57+
assertThat(queryEnhancer) //
58+
.isNotInstanceOf(JSqlParserQueryEnhancer.class);
59+
}
60+
5061
}

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

-8
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,6 @@ void doesNotPrefixAliasedFunctionCallNameWithDots() {
418418
assertThat(getEnhancer(query).applySorting(sort, "m")).endsWith("order by m.avg asc");
419419
}
420420

421-
@Test // DATAJPA-965, DATAJPA-970
422-
void doesNotPrefixAliasedFunctionCallNameWithDotsNativeQuery() {
423-
424-
// this is invalid since the '.' character is not allowed. Not in sql nor in JPQL.
425-
assertThatThrownBy(() -> new StringQuery("SELECT AVG(m.price) AS m.avg FROM Magazine m", true)) //
426-
.isInstanceOf(IllegalArgumentException.class);
427-
}
428-
429421
@Test // DATAJPA-965, DATAJPA-970
430422
void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpaces() {
431423

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

+3
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,9 @@ List<String> findAllAndSortByFunctionResultNamedParameter(@Param("namedParameter
679679
// GH-2607
680680
List<User> findByAttributesContains(String attribute);
681681

682+
@Query(value = "SELECT c.* FROM SD_User c WHERE c.firstname = :#{#example.firstname}", nativeQuery = true)
683+
List<User> nativeQueryWithSpELStatement(User example);
684+
682685
interface RolesAndFirstname {
683686

684687
String getFirstname();

0 commit comments

Comments
 (0)