Skip to content

Commit dc19e52

Browse files
committed
Allow to add a special parameter type from a Spring Data JPA extension
1 parent bf43e14 commit dc19e52

File tree

7 files changed

+124
-23
lines changed

7 files changed

+124
-23
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: 24 additions & 13 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);
@@ -92,9 +97,10 @@ private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrate
9297

9398
private final PersistenceProvider persistenceProvider;
9499

95-
public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor) {
100+
public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
101+
JpaQueryMethodFactory queryMethodFactory) {
96102

97-
super(em, extractor);
103+
super(em, extractor, queryMethodFactory);
98104
this.persistenceProvider = PersistenceProvider.fromEntityManager(em);
99105
}
100106

@@ -120,12 +126,13 @@ private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStra
120126
*
121127
* @param em
122128
* @param extractor
129+
* @param queryMethodFactory
123130
* @param evaluationContextProvider
124131
*/
125132
public DeclaredQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
126-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
133+
JpaQueryMethodFactory queryMethodFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
127134

128-
super(em, extractor);
135+
super(em, extractor, queryMethodFactory);
129136
this.evaluationContextProvider = evaluationContextProvider;
130137
}
131138

@@ -183,13 +190,15 @@ private static class CreateIfNotFoundQueryLookupStrategy extends AbstractQueryLo
183190
*
184191
* @param em
185192
* @param extractor
193+
* @param queryMethodFactory
186194
* @param createStrategy
187195
* @param lookupStrategy
188196
*/
189197
public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor,
190-
CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) {
198+
JpaQueryMethodFactory queryMethodFactory, CreateQueryLookupStrategy createStrategy,
199+
DeclaredQueryLookupStrategy lookupStrategy) {
191200

192-
super(em, extractor);
201+
super(em, extractor, queryMethodFactory);
193202

194203
this.createStrategy = createStrategy;
195204
this.lookupStrategy = lookupStrategy;
@@ -216,24 +225,26 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em,
216225
* @param em must not be {@literal null}.
217226
* @param key may be {@literal null}.
218227
* @param extractor must not be {@literal null}.
228+
* @param queryMethodFactory must not be {@literal null}.
219229
* @param evaluationContextProvider must not be {@literal null}.
220230
* @return
221231
*/
222232
public static QueryLookupStrategy create(EntityManager em, @Nullable Key key, QueryExtractor extractor,
223-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
233+
JpaQueryMethodFactory queryMethodFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
224234

225235
Assert.notNull(em, "EntityManager must not be null!");
226236
Assert.notNull(extractor, "QueryExtractor must not be null!");
227237
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
228238

229239
switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
230240
case CREATE:
231-
return new CreateQueryLookupStrategy(em, extractor);
241+
return new CreateQueryLookupStrategy(em, extractor, queryMethodFactory);
232242
case USE_DECLARED_QUERY:
233-
return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider);
243+
return new DeclaredQueryLookupStrategy(em, extractor, queryMethodFactory, evaluationContextProvider);
234244
case CREATE_IF_NOT_FOUND:
235-
return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor),
236-
new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider));
245+
return new CreateIfNotFoundQueryLookupStrategy(em, extractor, queryMethodFactory,
246+
new CreateQueryLookupStrategy(em, extractor, queryMethodFactory),
247+
new DeclaredQueryLookupStrategy(em, extractor, queryMethodFactory, evaluationContextProvider));
237248
default:
238249
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
239250
}

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

@@ -34,6 +34,7 @@
3434
import org.springframework.data.jpa.repository.query.AbstractJpaQuery;
3535
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
3636
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
37+
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
3738
import org.springframework.data.jpa.util.JpaMetamodel;
3839
import org.springframework.data.projection.ProjectionFactory;
3940
import org.springframework.data.querydsl.EntityPathResolver;
@@ -60,6 +61,7 @@
6061
* @author Christoph Strobl
6162
* @author Jens Schauder
6263
* @author Stefan Fussenegger
64+
* @author Reda.Housni-Alaoui
6365
*/
6466
public class JpaRepositoryFactory extends RepositoryFactorySupport {
6567

@@ -68,6 +70,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
6870
private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor;
6971

7072
private EntityPathResolver entityPathResolver;
73+
private JpaQueryMethodFactory queryMethodFactory;
7174

7275
/**
7376
* Creates a new {@link JpaRepositoryFactory}.
@@ -82,6 +85,7 @@ public JpaRepositoryFactory(EntityManager entityManager) {
8285
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
8386
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
8487
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
88+
this.queryMethodFactory = JpaQueryMethod.Factory.INSTANCE;
8589

8690
addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
8791

@@ -113,6 +117,17 @@ public void setEntityPathResolver(EntityPathResolver entityPathResolver) {
113117
this.entityPathResolver = entityPathResolver;
114118
}
115119

120+
/**
121+
* Configures the {@link JpaQueryMethodFactory} to be used. Defaults to {@link JpaQueryMethod.Factory#INSTANCE}.
122+
*
123+
* @param queryMethodFactory must not be {@literal null}.
124+
*/
125+
public void setQueryMethodFactory(JpaQueryMethodFactory queryMethodFactory) {
126+
Assert.notNull(queryMethodFactory, "QueryMethodFactory must not be null!");
127+
128+
this.queryMethodFactory = queryMethodFactory;
129+
}
130+
116131
/*
117132
* (non-Javadoc)
118133
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
@@ -174,7 +189,8 @@ protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFa
174189
@Override
175190
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
176191
QueryMethodEvaluationContextProvider evaluationContextProvider) {
177-
return Optional.of(JpaQueryLookupStrategy.create(entityManager, key, extractor, evaluationContextProvider));
192+
return Optional.of(
193+
JpaQueryLookupStrategy.create(entityManager, key, extractor, queryMethodFactory, evaluationContextProvider));
178194
}
179195

180196
/*

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.springframework.beans.factory.ObjectProvider;
2222
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
24+
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
2325
import org.springframework.data.mapping.context.MappingContext;
2426
import org.springframework.data.querydsl.EntityPathResolver;
2527
import org.springframework.data.querydsl.SimpleEntityPathResolver;
@@ -37,13 +39,15 @@
3739
* @author Eberhard Wolff
3840
* @author Mark Paluch
3941
* @author Jens Schauder
42+
* @author Reda.Housni-Alaoui
4043
* @param <T> the type of the repository
4144
*/
4245
public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
4346
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
4447

4548
private @Nullable EntityManager entityManager;
4649
private EntityPathResolver entityPathResolver;
50+
private JpaQueryMethodFactory queryMethodFactory;
4751

4852
/**
4953
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
@@ -84,6 +88,17 @@ public void setEntityPathResolver(ObjectProvider<EntityPathResolver> resolver) {
8488
this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE);
8589
}
8690

91+
/**
92+
* Configures the {@link JpaQueryMethodFactory} to be used. Will expect a canonical bean to be present but fallback to
93+
* {@link JpaQueryMethod.Factory#INSTANCE} in case none is available.
94+
*
95+
* @param resolver must not be {@literal null}.
96+
*/
97+
@Autowired
98+
public void setQueryMethodFactory(ObjectProvider<JpaQueryMethodFactory> resolver) {
99+
this.queryMethodFactory = resolver.getIfAvailable(() -> JpaQueryMethod.Factory.INSTANCE);
100+
}
101+
87102
/*
88103
* (non-Javadoc)
89104
* @see org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport#doCreateRepositoryFactory()
@@ -103,6 +118,7 @@ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityM
103118

104119
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
105120
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
121+
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
106122

107123
return jpaRepositoryFactory;
108124
}

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);
81+
QUERY_METHOD_FACTORY, EVALUATION_CONTEXT_PROVIDER);
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);
97+
QUERY_METHOD_FACTORY, EVALUATION_CONTEXT_PROVIDER);
9698
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Sort.class);
9799
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
98100

0 commit comments

Comments
 (0)