Skip to content

Commit 4448f34

Browse files
committed
Allow to add a special parameter type from a Spring Data JPA extension
1 parent e3ea134 commit 4448f34

File tree

7 files changed

+124
-25
lines changed

7 files changed

+124
-25
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
*
3434
* @author Thomas Darimont
3535
* @author Mark Paluch
36+
* @author Reda.Housni-Alaoui
3637
*/
3738
public class JpaParameters extends Parameters<JpaParameters, JpaParameter> {
3839

@@ -73,7 +74,7 @@ protected JpaParameters createFrom(List<JpaParameter> parameters) {
7374
* @author Thomas Darimont
7475
* @author Oliver Gierke
7576
*/
76-
static class JpaParameter extends Parameter {
77+
public static class JpaParameter extends Parameter {
7778

7879
private final @Nullable Temporal annotation;
7980
private @Nullable TemporalType temporalType;
@@ -83,7 +84,7 @@ static class JpaParameter extends Parameter {
8384
*
8485
* @param parameter must not be {@literal null}.
8586
*/
86-
JpaParameter(MethodParameter parameter) {
87+
protected JpaParameter(MethodParameter parameter) {
8788

8889
super(parameter);
8990

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

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
* @author Oliver Gierke
3939
* @author Thomas Darimont
4040
* @author Mark Paluch
41+
* @author Reda.Housni-Alaoui
4142
*/
4243
public final class JpaQueryLookupStrategy {
4344

@@ -56,17 +57,21 @@ private abstract static class AbstractQueryLookupStrategy implements QueryLookup
5657

5758
private final EntityManager em;
5859
private final QueryExtractor provider;
60+
private final JpaQueryMethodFactory queryMethodFactory;
5961

6062
/**
6163
* Creates a new {@link AbstractQueryLookupStrategy}.
6264
*
6365
* @param em
6466
* @param extractor
67+
* @param queryMethodFactory
6568
*/
66-
public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor) {
69+
public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
70+
JpaQueryMethodFactory queryMethodFactory) {
6771

6872
this.em = em;
6973
this.provider = extractor;
74+
this.queryMethodFactory = queryMethodFactory;
7075
}
7176

7277
/*
@@ -76,7 +81,7 @@ public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor) {
7681
@Override
7782
public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
7883
NamedQueries namedQueries) {
79-
return resolveQuery(new JpaQueryMethod(method, metadata, factory, provider), em, namedQueries);
84+
return resolveQuery(queryMethodFactory.build(method, metadata, factory, provider), em, namedQueries);
8085
}
8186

8287
protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries);
@@ -93,10 +98,9 @@ private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrate
9398
private final PersistenceProvider persistenceProvider;
9499
private final EscapeCharacter escape;
95100

96-
public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, EscapeCharacter escape) {
97-
98-
super(em, extractor);
101+
public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, EscapeCharacter escape, JpaQueryMethodFactory queryMethodFactory) {
99102

103+
super(em, extractor, queryMethodFactory);
100104
this.persistenceProvider = PersistenceProvider.fromEntityManager(em);
101105
this.escape = escape;
102106
}
@@ -123,12 +127,13 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra
123127
*
124128
* @param em
125129
* @param extractor
130+
* @param queryMethodFactory
126131
* @param evaluationContextProvider
127132
*/
128133
public DeclaredQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
129-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
134+
JpaQueryMethodFactory queryMethodFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
130135

131-
super(em, extractor);
136+
super(em, extractor, queryMethodFactory);
132137
this.evaluationContextProvider = evaluationContextProvider;
133138
}
134139

@@ -186,13 +191,15 @@ private static class CreateIfNotFoundQueryLookupStrategy extends AbstractQueryLo
186191
*
187192
* @param em
188193
* @param extractor
194+
* @param queryMethodFactory
189195
* @param createStrategy
190196
* @param lookupStrategy
191197
*/
192198
public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
193-
CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) {
199+
JpaQueryMethodFactory queryMethodFactory, CreateQueryLookupStrategy createStrategy,
200+
DeclaredQueryLookupStrategy lookupStrategy) {
194201

195-
super(em, extractor);
202+
super(em, extractor, queryMethodFactory);
196203

197204
this.createStrategy = createStrategy;
198205
this.lookupStrategy = lookupStrategy;
@@ -219,26 +226,27 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em,
219226
* @param em must not be {@literal null}.
220227
* @param key may be {@literal null}.
221228
* @param extractor must not be {@literal null}.
229+
* @param queryMethodFactory must not be {@literal null}.
222230
* @param evaluationContextProvider must not be {@literal null}.
223231
* @param escape
224232
* @return
225233
*/
226234
public static QueryLookupStrategy create(EntityManager em, @Nullable Key key, QueryExtractor extractor,
227-
QueryMethodEvaluationContextProvider evaluationContextProvider, EscapeCharacter escape) {
235+
JpaQueryMethodFactory queryMethodFactory, QueryMethodEvaluationContextProvider evaluationContextProvider, EscapeCharacter escape) {
228236

229237
Assert.notNull(em, "EntityManager must not be null!");
230238
Assert.notNull(extractor, "QueryExtractor must not be null!");
231239
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
232240

233241
switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
234242
case CREATE:
235-
return new CreateQueryLookupStrategy(em, extractor, escape);
243+
return new CreateQueryLookupStrategy(em, extractor, escape, queryMethodFactory);
236244
case USE_DECLARED_QUERY:
237-
return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider);
245+
return new DeclaredQueryLookupStrategy(em, extractor, queryMethodFactory, evaluationContextProvider);
238246
case CREATE_IF_NOT_FOUND:
239-
return new CreateIfNotFoundQueryLookupStrategy(em, extractor,
240-
new CreateQueryLookupStrategy(em, extractor, escape),
241-
new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider));
247+
return new CreateIfNotFoundQueryLookupStrategy(em, extractor,queryMethodFactory,
248+
new CreateQueryLookupStrategy(em, extractor, escape, queryMethodFactory),
249+
new DeclaredQueryLookupStrategy(em, extractor, queryMethodFactory, evaluationContextProvider));
242250
default:
243251
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
244252
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
* @author Nicolas Cirigliano
5454
* @author Mark Paluch
5555
* @author Сергей Цыпанов
56+
* @author Reda.Housni-Alaoui
5657
*/
5758
public class JpaQueryMethod extends QueryMethod {
5859

@@ -88,7 +89,7 @@ public class JpaQueryMethod extends QueryMethod {
8889
* @param factory must not be {@literal null}
8990
* @param extractor must not be {@literal null}
9091
*/
91-
public JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
92+
protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
9293
QueryExtractor extractor) {
9394

9495
super(method, metadata, factory);
@@ -403,4 +404,19 @@ StoredProcedureAttributes getProcedureAttributes() {
403404

404405
return storedProcedureAttributes;
405406
}
407+
408+
public static class Factory implements JpaQueryMethodFactory {
409+
410+
public static final Factory INSTANCE = new Factory();
411+
412+
private Factory() {
413+
414+
}
415+
416+
@Override
417+
public JpaQueryMethod build(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
418+
QueryExtractor extractor) {
419+
return new JpaQueryMethod(method, metadata, factory, extractor);
420+
}
421+
}
406422
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.query;
17+
18+
import java.lang.reflect.Method;
19+
20+
import org.springframework.data.jpa.provider.QueryExtractor;
21+
import org.springframework.data.projection.ProjectionFactory;
22+
import org.springframework.data.repository.core.RepositoryMetadata;
23+
24+
/**
25+
* @author Reda.Housni-Alaoui
26+
*/
27+
public interface JpaQueryMethodFactory {
28+
29+
/**
30+
* Creates a {@link JpaQueryMethod}.
31+
*
32+
* @param method must not be {@literal null}
33+
* @param metadata must not be {@literal null}
34+
* @param factory must not be {@literal null}
35+
* @param extractor must not be {@literal null}
36+
*/
37+
JpaQueryMethod build(Method method, RepositoryMetadata metadata, ProjectionFactory factory, QueryExtractor extractor);
38+
39+
}

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18-
import static org.springframework.data.querydsl.QuerydslUtils.*;
18+
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
1919

2020
import lombok.extern.slf4j.Slf4j;
2121

@@ -35,6 +35,7 @@
3535
import org.springframework.data.jpa.repository.query.EscapeCharacter;
3636
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
3737
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
38+
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
3839
import org.springframework.data.jpa.util.JpaMetamodel;
3940
import org.springframework.data.projection.ProjectionFactory;
4041
import org.springframework.data.querydsl.EntityPathResolver;
@@ -61,6 +62,7 @@
6162
* @author Christoph Strobl
6263
* @author Jens Schauder
6364
* @author Stefan Fussenegger
65+
* @author Reda.Housni-Alaoui
6466
*/
6567
public class JpaRepositoryFactory extends RepositoryFactorySupport {
6668

@@ -70,6 +72,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
7072

7173
private EntityPathResolver entityPathResolver;
7274
private EscapeCharacter escapeCharacter = EscapeCharacter.of('\\');
75+
private JpaQueryMethodFactory queryMethodFactory;
7376

7477
/**
7578
* Creates a new {@link JpaRepositoryFactory}.
@@ -84,6 +87,7 @@ public JpaRepositoryFactory(EntityManager entityManager) {
8487
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
8588
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
8689
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
90+
this.queryMethodFactory = JpaQueryMethod.Factory.INSTANCE;
8791

8892
addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
8993

@@ -124,6 +128,17 @@ public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
124128
this.escapeCharacter = escapeCharacter;
125129
}
126130

131+
/**
132+
* Configures the {@link JpaQueryMethodFactory} to be used. Defaults to {@link JpaQueryMethod.Factory#INSTANCE}.
133+
*
134+
* @param queryMethodFactory must not be {@literal null}.
135+
*/
136+
public void setQueryMethodFactory(JpaQueryMethodFactory queryMethodFactory) {
137+
Assert.notNull(queryMethodFactory, "QueryMethodFactory must not be null!");
138+
139+
this.queryMethodFactory = queryMethodFactory;
140+
}
141+
127142
/*
128143
* (non-Javadoc)
129144
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
@@ -186,7 +201,8 @@ protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFa
186201
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
187202
QueryMethodEvaluationContextProvider evaluationContextProvider) {
188203
return Optional
189-
.of(JpaQueryLookupStrategy.create(entityManager, key, extractor, evaluationContextProvider, escapeCharacter));
204+
.of(
205+
JpaQueryLookupStrategy.create(entityManager, key, extractor, queryMethodFactory, evaluationContextProvider, escapeCharacter));
190206
}
191207

192208
/*

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.springframework.beans.factory.ObjectProvider;
2222
import org.springframework.beans.factory.annotation.Autowired;
2323
import org.springframework.data.jpa.repository.query.EscapeCharacter;
24+
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
25+
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
2426
import org.springframework.data.mapping.context.MappingContext;
2527
import org.springframework.data.querydsl.EntityPathResolver;
2628
import org.springframework.data.querydsl.SimpleEntityPathResolver;
@@ -38,6 +40,7 @@
3840
* @author Eberhard Wolff
3941
* @author Mark Paluch
4042
* @author Jens Schauder
43+
* @author Reda.Housni-Alaoui
4144
* @param <T> the type of the repository
4245
*/
4346
public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
@@ -46,6 +49,7 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
4649
private @Nullable EntityManager entityManager;
4750
private EntityPathResolver entityPathResolver;
4851
private EscapeCharacter escapeCharacter = EscapeCharacter.of('\\');
52+
private JpaQueryMethodFactory queryMethodFactory;
4953

5054
/**
5155
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
@@ -86,6 +90,17 @@ public void setEntityPathResolver(ObjectProvider<EntityPathResolver> resolver) {
8690
this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE);
8791
}
8892

93+
/**
94+
* Configures the {@link JpaQueryMethodFactory} to be used. Will expect a canonical bean to be present but fallback to
95+
* {@link JpaQueryMethod.Factory#INSTANCE} in case none is available.
96+
*
97+
* @param resolver must not be {@literal null}.
98+
*/
99+
@Autowired
100+
public void setQueryMethodFactory(ObjectProvider<JpaQueryMethodFactory> resolver) {
101+
this.queryMethodFactory = resolver.getIfAvailable(() -> JpaQueryMethod.Factory.INSTANCE);
102+
}
103+
89104
/*
90105
* (non-Javadoc)
91106
* @see org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport#doCreateRepositoryFactory()
@@ -106,6 +121,8 @@ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityM
106121
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
107122
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
108123
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);
124+
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
125+
109126
return jpaRepositoryFactory;
110127
}
111128

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.ArgumentMatchers.*;
20-
import static org.mockito.Mockito.*;
18+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
19+
import static org.mockito.ArgumentMatchers.anyString;
20+
import static org.mockito.Mockito.when;
2121

2222
import java.lang.reflect.Method;
2323
import java.util.List;
@@ -50,11 +50,13 @@
5050
* @author Oliver Gierke
5151
* @author Thomas Darimont
5252
* @author Jens Schauder
53+
* @author Reda.Housni-Alaoui
5354
*/
5455
@RunWith(MockitoJUnitRunner.class)
5556
public class JpaQueryLookupStrategyUnitTests {
5657

5758
private static final QueryMethodEvaluationContextProvider EVALUATION_CONTEXT_PROVIDER = QueryMethodEvaluationContextProvider.DEFAULT;
59+
private static final JpaQueryMethodFactory QUERY_METHOD_FACTORY = JpaQueryMethod.Factory.INSTANCE;
5860

5961
@Mock EntityManager em;
6062
@Mock EntityManagerFactory emf;
@@ -76,7 +78,7 @@ public void setUp() {
7678
public void invalidAnnotatedQueryCausesException() throws Exception {
7779

7880
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, Key.CREATE_IF_NOT_FOUND, extractor,
79-
EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.of('\\'));
81+
QUERY_METHOD_FACTORY, EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.of('\\'));
8082
Method method = UserRepository.class.getMethod("findByFoo", String.class);
8183
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
8284

@@ -92,7 +94,7 @@ public void invalidAnnotatedQueryCausesException() throws Exception {
9294
public void sholdThrowMorePreciseExceptionIfTryingToUsePaginationInNativeQueries() throws Exception {
9395

9496
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, Key.CREATE_IF_NOT_FOUND, extractor,
95-
EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.of('\\'));
97+
QUERY_METHOD_FACTORY, EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.of('\\'));
9698
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Sort.class);
9799
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
98100

0 commit comments

Comments
 (0)