Skip to content

Commit dc37395

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. See #2018.
1 parent cf2204c commit dc37395

File tree

3 files changed

+65
-17
lines changed

3 files changed

+65
-17
lines changed

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

+35-15
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121

2222
import org.slf4j.Logger;
2323
import org.slf4j.LoggerFactory;
24-
2524
import org.springframework.data.jpa.repository.Query;
2625
import org.springframework.data.projection.ProjectionFactory;
2726
import org.springframework.data.repository.core.NamedQueries;
2827
import org.springframework.data.repository.core.RepositoryMetadata;
2928
import org.springframework.data.repository.query.QueryLookupStrategy;
3029
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
30+
import org.springframework.data.repository.query.QueryMethod;
3131
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3232
import org.springframework.data.repository.query.RepositoryQuery;
3333
import org.springframework.lang.Nullable;
@@ -46,6 +46,12 @@ public final class JpaQueryLookupStrategy {
4646

4747
private static final Logger LOG = LoggerFactory.getLogger(JpaQueryLookupStrategy.class);
4848

49+
/**
50+
* A null-value instance used to signal if no declared query could be found. It checks many different formats before
51+
* falling through to this value object.
52+
*/
53+
private static final RepositoryQuery NO_QUERY = new NoQuery();
54+
4955
/**
5056
* Private constructor to prevent instantiation.
5157
*/
@@ -161,24 +167,20 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em,
161167
}
162168

163169
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
164-
getCountQuery(method, namedQueries, em),
165-
evaluationContextProvider);
170+
getCountQuery(method, namedQueries, em), evaluationContextProvider);
166171
}
167172

168173
String name = method.getNamedQueryName();
169174
if (namedQueries.hasQuery(name)) {
170-
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name), getCountQuery(method, namedQueries, em),
171-
evaluationContextProvider);
175+
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
176+
getCountQuery(method, namedQueries, em), evaluationContextProvider);
172177
}
173178

174179
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
175180

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

184186
@Nullable
@@ -248,11 +250,13 @@ public CreateIfNotFoundQueryLookupStrategy(EntityManager em, JpaQueryMethodFacto
248250
@Override
249251
protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
250252

251-
try {
252-
return lookupStrategy.resolveQuery(method, em, namedQueries);
253-
} catch (IllegalStateException e) {
254-
return createStrategy.resolveQuery(method, em, namedQueries);
253+
RepositoryQuery lookupQuery = lookupStrategy.resolveQuery(method, em, namedQueries);
254+
255+
if (lookupQuery != NO_QUERY) {
256+
return lookupQuery;
255257
}
258+
259+
return createStrategy.resolveQuery(method, em, namedQueries);
256260
}
257261
}
258262

@@ -286,4 +290,20 @@ public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory
286290
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
287291
}
288292
}
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+
}
289309
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -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;

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

+27-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.mockito.junit.jupiter.MockitoExtension;
3434
import org.mockito.junit.jupiter.MockitoSettings;
3535
import org.mockito.quality.Strictness;
36-
3736
import org.springframework.data.domain.Page;
3837
import org.springframework.data.domain.Pageable;
3938
import org.springframework.data.domain.Sort;
@@ -167,6 +166,30 @@ void prefersDeclaredQuery() throws Exception {
167166
assertThat(repositoryQuery).isInstanceOf(AbstractStringBasedJpaQuery.class);
168167
}
169168

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

172195
@Query("something absurd")
@@ -183,5 +206,8 @@ interface UserRepository extends Repository<User, Integer> {
183206

184207
@Query(value = "something absurd", name = "my-query-name")
185208
User annotatedQueryWithQueryAndQueryName();
209+
210+
// This is a named query with Sort parameter, which isn't supported
211+
List<User> customNamedQuery(String firstname, Sort sort);
186212
}
187213
}

0 commit comments

Comments
 (0)