Skip to content

Commit b26f8e8

Browse files
Move validation flag to repository factory
1 parent 637a2e0 commit b26f8e8

File tree

10 files changed

+114
-38
lines changed

10 files changed

+114
-38
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
8383
private static final String ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE = "enableDefaultTransactions";
8484
private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup";
8585
private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter";
86+
private static final String QUERY_VALIDATION = "queryValidation";
8687

8788
private final Map<Object, String> entityManagerRefs = new LinkedHashMap<>();
8889

@@ -118,6 +119,7 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
118119
builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
119120
builder.addPropertyReference("entityManager", entityManagerRefs.get(source));
120121
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
122+
getQueryValidation(source).ifPresent(it -> builder.addPropertyValue(QUERY_VALIDATION, it));
121123
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
122124
}
123125

@@ -139,6 +141,15 @@ private static Optional<Character> getEscapeCharacter(RepositoryConfigurationSou
139141
}
140142
}
141143

144+
private static Optional<Boolean> getQueryValidation(RepositoryConfigurationSource source) {
145+
146+
try {
147+
return source.getAttribute(QUERY_VALIDATION, Boolean.class);
148+
} catch (IllegalArgumentException ___) {
149+
return Optional.empty();
150+
}
151+
}
152+
142153
@Override
143154
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
144155

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ enum JpaQueryFactory {
3838
*/
3939
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
4040
@Nullable String countQueryString, QueryRewriter queryRewriter,
41-
ValueExpressionDelegate valueExpressionDelegate) {
41+
ValueExpressionDelegate valueExpressionDelegate, boolean validation) {
4242

4343
if (method.isScrollQuery()) {
4444
throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");
4545
}
4646

4747
return method.isNativeQuery()
4848
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate)
49-
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
49+
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate, validation);
5050
}
5151

5252
/**

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,21 @@ protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewr
113113
private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy {
114114

115115
private final EscapeCharacter escape;
116+
private final boolean validate;
116117

117118
public CreateQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
118-
QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
119+
QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape, boolean validate) {
119120

120121
super(em, queryMethodFactory, queryRewriterProvider);
121122

122123
this.escape = escape;
124+
this.validate = validate;
123125
}
124126

125127
@Override
126128
protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,
127129
NamedQueries namedQueries) {
128-
return new PartTreeJpaQuery(method, em, escape);
130+
return new PartTreeJpaQuery(method, em, escape, validate);
129131
}
130132
}
131133

@@ -140,20 +142,24 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
140142
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
141143

142144
private final ValueExpressionDelegate valueExpressionDelegate;
145+
private final boolean validation;
143146

144147
/**
145148
* Creates a new {@link DeclaredQueryLookupStrategy}.
146149
*
147150
* @param em must not be {@literal null}.
148151
* @param queryMethodFactory must not be {@literal null}.
149-
* @param evaluationContextProvider must not be {@literal null}.
152+
* @param delegate must not be {@literal null}.
153+
* @param queryRewriterProvider must not be {@literal null}.
154+
* @param validation indicate if queries are validated on startup.
150155
*/
151156
public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
152-
ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) {
157+
ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider, boolean validation) {
153158

154159
super(em, queryMethodFactory, queryRewriterProvider);
155160

156161
this.valueExpressionDelegate = delegate;
162+
this.validation = validation;
157163
}
158164

159165
@Override
@@ -172,13 +178,13 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer
172178
}
173179

174180
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
175-
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
181+
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate, validation);
176182
}
177183

178184
String name = method.getNamedQueryName();
179185
if (namedQueries.hasQuery(name)) {
180186
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
181-
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
187+
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate, validation);
182188
}
183189

184190
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
@@ -285,6 +291,22 @@ public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory
285291
queryRewriterProvider, escape);
286292
}
287293

294+
/**
295+
* Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
296+
*
297+
* @param em must not be {@literal null}.
298+
* @param queryMethodFactory must not be {@literal null}.
299+
* @param key may be {@literal null}.
300+
* @param delegate must not be {@literal null}.
301+
* @param queryRewriterProvider must not be {@literal null}.
302+
* @param escape must not be {@literal null}.
303+
*/
304+
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
305+
@Nullable Key key, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider,
306+
EscapeCharacter escape) {
307+
return create(em, queryMethodFactory, key, delegate, queryRewriterProvider, escape, true);
308+
}
309+
288310
/**
289311
* Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
290312
*
@@ -297,18 +319,18 @@ public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory
297319
*/
298320
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
299321
@Nullable Key key, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider,
300-
EscapeCharacter escape) {
322+
EscapeCharacter escape, boolean queryValidation) {
301323

302324
Assert.notNull(em, "EntityManager must not be null");
303325
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
304326

305327
return switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
306-
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
328+
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape, queryValidation);
307329
case USE_DECLARED_QUERY ->
308-
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider);
330+
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider, queryValidation);
309331
case CREATE_IF_NOT_FOUND -> new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
310-
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
311-
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider),
332+
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape, queryValidation),
333+
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider, queryValidation),
312334
queryRewriterProvider);
313335
default -> throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
314336
};

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
7171
* @param em must not be {@literal null}.
7272
*/
7373
PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) {
74-
this(method, em, EscapeCharacter.DEFAULT);
74+
this(method, em, EscapeCharacter.DEFAULT, true);
7575
}
7676

7777
/**
@@ -81,7 +81,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
8181
* @param em must not be {@literal null}.
8282
* @param escape character used for escaping characters used as patterns in LIKE-expressions.
8383
*/
84-
PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, EscapeCharacter escape) {
84+
PartTreeJpaQuery(JpaQueryMethod method, EntityManager em, EscapeCharacter escape, boolean validate) {
8585

8686
super(method, em);
8787

@@ -99,7 +99,9 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
9999
try {
100100

101101
this.tree = new PartTree(method.getName(), domainClass);
102-
validate(tree, parameters, method.toString());
102+
if(validate) {
103+
validate(tree, parameters, method.toString());
104+
}
103105
this.countQuery = new CountQueryPreparer(recreationRequired);
104106
this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(recreationRequired);
105107

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
4646
* @param valueExpressionDelegate must not be {@literal null}
4747
*/
4848
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String countQueryString,
49-
QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
50-
this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, valueExpressionDelegate);
49+
QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate, boolean validation) {
50+
this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, valueExpressionDelegate, validation);
5151
}
5252

5353
/**
@@ -61,15 +61,17 @@ public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String
6161
* @param valueExpressionDelegate must not be {@literal null}
6262
*/
6363
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString, QueryRewriter queryRewriter,
64-
ValueExpressionDelegate valueExpressionDelegate) {
64+
ValueExpressionDelegate valueExpressionDelegate, boolean validation) {
6565

6666
super(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
6767

68-
validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s", method);
68+
if(validation) {
69+
validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s", method);
6970

70-
if (method.isPageQuery()) {
71-
validateQuery(getCountQuery().getQueryString(),
71+
if (method.isPageQuery()) {
72+
validateQuery(getCountQuery().getQueryString(),
7273
String.format("Count query validation failed for method %s", method));
74+
}
7375
}
7476
}
7577

@@ -81,11 +83,6 @@ public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin
8183
*/
8284
private void validateQuery(String query, String errorMessage, Object... arguments) {
8385

84-
String property = SpringProperties.getProperty("spring.data.jpa.query.validate");
85-
if(property != null && !Boolean.parseBoolean(property)) {
86-
return;
87-
}
88-
8986
if (getQueryMethod().isProcedureQuery()) {
9087
return;
9188
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
public class DefaultJpaContext implements JpaContext {
3737

3838
private final MultiValueMap<Class<?>, EntityManager> entityManagers;
39+
private boolean validateQueries;
3940

4041
/**
4142
* Creates a new {@link DefaultJpaContext} for the given {@link Set} of {@link EntityManager}s.

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.beans.BeanUtils;
3232
import org.springframework.beans.BeansException;
3333
import org.springframework.beans.factory.BeanFactory;
34+
import org.springframework.core.SpringProperties;
3435
import org.springframework.dao.InvalidDataAccessApiUsageException;
3536
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
3637
import org.springframework.data.jpa.provider.PersistenceProvider;
@@ -90,6 +91,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
9091
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
9192
private JpaQueryMethodFactory queryMethodFactory;
9293
private QueryRewriterProvider queryRewriterProvider;
94+
private boolean queryValidation;
9395

9496
/**
9597
* Creates a new {@link JpaRepositoryFactory}.
@@ -120,6 +122,8 @@ public JpaRepositoryFactory(EntityManager entityManager) {
120122
}
121123

122124
this.crudMethodMetadata = crudMethodMetadataPostProcessor.getCrudMethodMetadata();
125+
String p = SpringProperties.getProperty("spring.data.jpa.query.validate");
126+
this.queryValidation = p == null || Boolean.parseBoolean(p);
123127
}
124128

125129
@Override
@@ -167,6 +171,15 @@ public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
167171
this.escapeCharacter = escapeCharacter;
168172
}
169173

174+
/**
175+
* Configures if spring based queries should be validated on creation
176+
*
177+
* @param queryValidation a character used for escaping in certain like expressions.
178+
* @since 3.4
179+
*/
180+
public void setQueryValidation(boolean queryValidation) {
181+
this.queryValidation = queryValidation;
182+
}
170183
/**
171184
* Configures the {@link JpaQueryMethodFactory} to be used. Defaults to {@link DefaultJpaQueryMethodFactory}.
172185
*
@@ -240,7 +253,7 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key
240253
ValueExpressionDelegate valueExpressionDelegate) {
241254
return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key,
242255
new CachingValueExpressionDelegate(valueExpressionDelegate),
243-
queryRewriterProvider, escapeCharacter));
256+
queryRewriterProvider, escapeCharacter, queryValidation));
244257
}
245258

246259

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
4848
private @Nullable EntityManager entityManager;
4949
private EntityPathResolver entityPathResolver;
5050
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
51+
private Boolean queryValidation;
5152
private JpaQueryMethodFactory queryMethodFactory;
5253

5354
/**
@@ -116,6 +117,9 @@ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityM
116117
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
117118
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
118119
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);
120+
if(queryValidation != null) {
121+
jpaRepositoryFactory.setQueryValidation(queryValidation);
122+
}
119123

120124
if (queryMethodFactory != null) {
121125
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
@@ -136,4 +140,12 @@ public void setEscapeCharacter(char escapeCharacter) {
136140

137141
this.escapeCharacter = EscapeCharacter.of(escapeCharacter);
138142
}
143+
144+
/**
145+
* @param queryValidation
146+
* @since 3.4
147+
*/
148+
public void setQueryValidation(Boolean queryValidation) {
149+
this.queryValidation = queryValidation;
150+
}
139151
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ void createsNormalQueryForJpaManagedReturnTypes() throws Exception {
6767

6868
JpaQueryMethod method = getMethod("findRolesByEmailAddress", String.class);
6969
AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
70-
ValueExpressionDelegate.create());
70+
ValueExpressionDelegate.create(), true);
7171

7272
jpaQuery.createJpaQuery(method.getAnnotatedQuery(), Sort.unsorted(), null,
7373
method.getResultProcessor().getReturnedType());

0 commit comments

Comments
 (0)