Skip to content

Commit e228cc2

Browse files
committed
JpaQueryLookupStrategy shouldn't use exceptions for flow control.
By using exceptions for flow control, other critical exceptions are getting masked. The lack of a resolvable query should instead leverage some sort of null value object. Closes #2018.
1 parent 406af9b commit e228cc2

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

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

+32-10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.repository.core.RepositoryMetadata;
2929
import org.springframework.data.repository.query.QueryLookupStrategy;
3030
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
31+
import org.springframework.data.repository.query.QueryMethod;
3132
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3233
import org.springframework.data.repository.query.RepositoryQuery;
3334
import org.springframework.lang.Nullable;
@@ -47,6 +48,12 @@ public final class JpaQueryLookupStrategy {
4748

4849
private static final Log LOG = LogFactory.getLog(JpaQueryLookupStrategy.class);
4950

51+
/**
52+
* A null-value instance used to signal if no declared query could be found. It checks many different formats before
53+
* falling through to this value object.
54+
*/
55+
private static final RepositoryQuery NO_QUERY = new NoQuery();
56+
5057
/**
5158
* Private constructor to prevent instantiation.
5259
*/
@@ -172,12 +179,9 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
172179

173180
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
174181

175-
if (null != query) {
176-
return query;
177-
}
178-
179-
throw new IllegalStateException(
180-
String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
182+
return query != null //
183+
? query //
184+
: NO_QUERY;
181185
}
182186

183187
@Nullable
@@ -245,11 +249,13 @@ public CreateIfNotFoundQueryLookupStrategy(EntityManager em, JpaQueryMethodFacto
245249
protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,
246250
NamedQueries namedQueries) {
247251

248-
try {
249-
return lookupStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
250-
} catch (IllegalStateException e) {
251-
return createStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
252+
RepositoryQuery lookupQuery = lookupStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
253+
254+
if (lookupQuery != NO_QUERY) {
255+
return lookupQuery;
252256
}
257+
258+
return createStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
253259
}
254260
}
255261

@@ -284,4 +290,20 @@ public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory
284290
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
285291
}
286292
}
293+
294+
/**
295+
* A null value type that represents the lack of a defined query.
296+
*/
297+
static class NoQuery implements RepositoryQuery {
298+
299+
@Override
300+
public Object execute(Object[] parameters) {
301+
throw new IllegalStateException("NoQuery should not be executed!");
302+
}
303+
304+
@Override
305+
public QueryMethod getQueryMethod() {
306+
throw new IllegalStateException("NoQuery does not have a QueryMethod!");
307+
}
308+
}
287309
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/User.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
*/
1616
package org.springframework.data.jpa.domain.sample;
1717

18+
import jakarta.persistence.*;
19+
1820
import java.util.Arrays;
1921
import java.util.Date;
2022
import java.util.HashSet;
2123
import java.util.Set;
2224

23-
import jakarta.persistence.*;
24-
2525
/**
2626
* Domain class representing a person emphasizing the use of {@code AbstractEntity}. No declaration of an id is
2727
* required. The id is typed by the parameterizable superclass.
@@ -32,6 +32,7 @@
3232
* @author Jens Schauder
3333
* @author Jeff Sheets
3434
* @author JyotirmoyVS
35+
* @author Greg Turnquist
3536
*/
3637
@Entity
3738
@NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }),
@@ -91,7 +92,8 @@
9192
@Table(name = "SD_User")
9293
public class User {
9394

94-
@Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id;
95+
@Id
96+
@GeneratedValue(strategy = GenerationType.AUTO) private Integer id;
9597
private String firstname;
9698
private String lastname;
9799
private int age;

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

+27
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,30 @@ void prefersDeclaredQuery() throws Exception {
169169
assertThat(repositoryQuery).isInstanceOf(AbstractStringBasedJpaQuery.class);
170170
}
171171

172+
@Test // GH-2018
173+
void namedQueryWithSortShouldThrowIllegalStateException() throws NoSuchMethodException {
174+
175+
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
176+
EVALUATION_CONTEXT_PROVIDER, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
177+
178+
Method method = UserRepository.class.getMethod("customNamedQuery", String.class, Sort.class);
179+
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
180+
181+
assertThatIllegalStateException()
182+
.isThrownBy(() -> strategy.resolveQuery(method, metadata, projectionFactory, namedQueries))
183+
.withMessageContaining(
184+
"is backed by a NamedQuery and must not contain a sort parameter as we cannot modify the query! Use @Query instead!");
185+
}
186+
187+
@Test // GH-2018
188+
void noQueryShouldNotBeInvoked() {
189+
190+
RepositoryQuery query = new JpaQueryLookupStrategy.NoQuery();
191+
192+
assertThatIllegalStateException().isThrownBy(() -> query.execute(new Object[] {}));
193+
assertThatIllegalStateException().isThrownBy(() -> query.getQueryMethod());
194+
}
195+
172196
interface UserRepository extends Repository<User, Integer> {
173197

174198
@Query("something absurd")
@@ -185,5 +209,8 @@ interface UserRepository extends Repository<User, Integer> {
185209

186210
@Query(value = "something absurd", name = "my-query-name")
187211
User annotatedQueryWithQueryAndQueryName();
212+
213+
// This is a named query with Sort parameter, which isn't supported
214+
List<User> customNamedQuery(String firstname, Sort sort);
188215
}
189216
}

0 commit comments

Comments
 (0)