Skip to content

Commit ae56b1d

Browse files
committed
Adopt JpaParameters to reflect the actual parameter type when using generics.
Closes #3254
1 parent 9048aea commit ae56b1d

11 files changed

+142
-65
lines changed

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

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
import java.lang.reflect.Method;
2121
import java.util.Date;
2222
import java.util.List;
23+
import java.util.function.Function;
2324

2425
import org.springframework.core.MethodParameter;
2526
import org.springframework.data.jpa.repository.Temporal;
2627
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
2728
import org.springframework.data.repository.query.Parameter;
2829
import org.springframework.data.repository.query.Parameters;
30+
import org.springframework.data.repository.query.ParametersSource;
31+
import org.springframework.data.util.TypeInformation;
2932
import org.springframework.lang.Nullable;
3033

3134
/**
@@ -41,16 +44,42 @@ public class JpaParameters extends Parameters<JpaParameters, JpaParameter> {
4144
* Creates a new {@link JpaParameters} instance from the given {@link Method}.
4245
*
4346
* @param method must not be {@literal null}.
47+
* @deprecated since 3.2.1, use {@link #JpaParameters(ParametersSource)} instead.
4448
*/
49+
@Deprecated(since = "3.2.1", forRemoval = true)
4550
public JpaParameters(Method method) {
46-
super(method);
51+
this(ParametersSource.of(method), null);
52+
}
53+
54+
/**
55+
* Creates a new {@link JpaParameters} instance from the given {@link ParametersSource}.
56+
*
57+
* @param parametersSource must not be {@literal null}.
58+
* @since 3.2.1
59+
*/
60+
public JpaParameters(ParametersSource parametersSource) {
61+
super(parametersSource,
62+
methodParameter -> new JpaParameter(methodParameter, parametersSource.getDomainTypeInformation()));
63+
}
64+
65+
/**
66+
* Creates a new {@link JpaParameters} instance from the given {@link Method}.
67+
*
68+
* @param parametersSource must not be {@literal null}.
69+
* @param parameterFactory must not be {@literal null}.
70+
* @since 3.2.1
71+
*/
72+
protected JpaParameters(ParametersSource parametersSource,
73+
Function<MethodParameter, JpaParameter> parameterFactory) {
74+
super(parametersSource, parameterFactory);
4775
}
4876

4977
private JpaParameters(List<JpaParameter> parameters) {
5078
super(parameters);
5179
}
5280

5381
@Override
82+
@Deprecated(forRemoval = true)
5483
protected JpaParameter createParameter(MethodParameter parameter) {
5584
return new JpaParameter(parameter);
5685
}
@@ -82,14 +111,31 @@ public static class JpaParameter extends Parameter {
82111
* Creates a new {@link JpaParameter}.
83112
*
84113
* @param parameter must not be {@literal null}.
114+
* @deprecated since 3.2.1
85115
*/
116+
@Deprecated(since = "3.2.1", forRemoval = true)
86117
protected JpaParameter(MethodParameter parameter) {
87118

88119
super(parameter);
89120

90121
this.annotation = parameter.getParameterAnnotation(Temporal.class);
91122
this.temporalType = null;
123+
if (!isDateParameter() && hasTemporalParamAnnotation()) {
124+
throw new IllegalArgumentException(
125+
Temporal.class.getSimpleName() + " annotation is only allowed on Date parameter");
126+
}
127+
}
128+
129+
/**
130+
* Creates a new {@link JpaParameter}.
131+
*
132+
* @param parameter must not be {@literal null}.
133+
*/
134+
protected JpaParameter(MethodParameter parameter, TypeInformation<?> domainType) {
92135

136+
super(parameter, domainType);
137+
this.annotation = parameter.getParameterAnnotation(Temporal.class);
138+
this.temporalType = null;
93139
if (!isDateParameter() && hasTemporalParamAnnotation()) {
94140
throw new IllegalArgumentException(
95141
Temporal.class.getSimpleName() + " annotation is only allowed on Date parameter");

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.data.repository.core.RepositoryMetadata;
4343
import org.springframework.data.repository.query.Parameter;
4444
import org.springframework.data.repository.query.Parameters;
45+
import org.springframework.data.repository.query.ParametersSource;
4546
import org.springframework.data.repository.query.QueryMethod;
4647
import org.springframework.data.repository.util.QueryExecutionConverters;
4748
import org.springframework.data.util.Lazy;
@@ -447,8 +448,8 @@ private <T> T getMergedOrDefaultAnnotationValue(String attribute, Class annotati
447448
}
448449

449450
@Override
450-
protected JpaParameters createParameters(Method method) {
451-
return new JpaParameters(method);
451+
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
452+
return new JpaParameters(parametersSource);
452453
}
453454

454455
@Override

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
import org.junit.jupiter.api.Assertions;
88
import org.junit.jupiter.api.Test;
99
import org.junit.jupiter.api.extension.ExtendWith;
10+
1011
import org.springframework.beans.factory.annotation.Autowired;
11-
import org.springframework.data.jpa.repository.query.HibernateJpaParametersParameterAccessor;
12-
import org.springframework.data.jpa.repository.query.JpaParameters;
12+
import org.springframework.data.repository.query.ParametersSource;
1313
import org.springframework.test.context.ContextConfiguration;
1414
import org.springframework.test.context.junit.jupiter.SpringExtension;
1515
import org.springframework.transaction.PlatformTransactionManager;
@@ -48,7 +48,7 @@ void withinTransaction() throws Exception {
4848
private void parametersCanGetAccessesOutsideTransaction() throws NoSuchMethodException {
4949

5050
Method method = EntityManager.class.getMethod("flush");
51-
JpaParameters parameters = new JpaParameters(method);
51+
JpaParameters parameters = new JpaParameters(ParametersSource.of(method));
5252
HibernateJpaParametersParameterAccessor accessor = new HibernateJpaParametersParameterAccessor(parameters,
5353
new Object[] {}, em);
5454
Assertions.assertEquals(0, accessor.getValues().length);

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
import org.junit.jupiter.api.Test;
1616
import org.junit.jupiter.api.extension.ExtendWith;
1717
import org.mockito.ArgumentCaptor;
18+
1819
import org.springframework.data.jpa.domain.sample.User;
19-
import org.springframework.data.jpa.provider.PersistenceProvider;
20+
import org.springframework.data.repository.query.ParametersSource;
2021
import org.springframework.test.context.ContextConfiguration;
2122
import org.springframework.test.context.junit.jupiter.SpringExtension;
2223

@@ -42,7 +43,7 @@ void createsJpaParametersParameterAccessor() throws Exception {
4243

4344
Method withNativeQuery = SampleRepository.class.getMethod("withNativeQuery", Integer.class);
4445
Object[] values = { null };
45-
JpaParameters parameters = new JpaParameters(withNativeQuery);
46+
JpaParameters parameters = new JpaParameters(ParametersSource.of(withNativeQuery));
4647
JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor(parameters, values);
4748

4849
bind(parameters, accessor);
@@ -55,7 +56,7 @@ void createsHibernateParametersParameterAccessor() throws Exception {
5556

5657
Method withNativeQuery = SampleRepository.class.getMethod("withNativeQuery", Integer.class);
5758
Object[] values = { null };
58-
JpaParameters parameters = new JpaParameters(withNativeQuery);
59+
JpaParameters parameters = new JpaParameters(ParametersSource.of(withNativeQuery));
5960
JpaParametersParameterAccessor accessor = new HibernateJpaParametersParameterAccessor(parameters, values, em);
6061

6162
bind(parameters, accessor);

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,24 @@
1818
import static jakarta.persistence.TemporalType.*;
1919
import static org.assertj.core.api.Assertions.*;
2020

21+
import jakarta.persistence.TemporalType;
22+
2123
import java.lang.reflect.Method;
2224
import java.util.Date;
2325

24-
import jakarta.persistence.TemporalType;
25-
2626
import org.junit.jupiter.api.Test;
27+
2728
import org.springframework.data.jpa.repository.Temporal;
2829
import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
30+
import org.springframework.data.repository.Repository;
31+
import org.springframework.data.repository.query.ParametersSource;
2932

3033
/**
3134
* Unit tests for {@link JpaParameters}.
3235
*
3336
* @author Oliver Gierke
3437
* @author Jens Schauder
38+
* @author Mark Paluch
3539
*/
3640
class JpaParametersUnitTests {
3741

@@ -40,7 +44,7 @@ void findsTemporalParameterConfiguration() throws Exception {
4044

4145
Method method = SampleRepository.class.getMethod("foo", Date.class, String.class);
4246

43-
JpaParameters parameters = new JpaParameters(method);
47+
JpaParameters parameters = new JpaParameters(ParametersSource.of(method));
4448

4549
JpaParameter parameter = parameters.getBindableParameter(0);
4650
assertThat(parameter.isSpecialParameter()).isFalse();
@@ -51,7 +55,7 @@ void findsTemporalParameterConfiguration() throws Exception {
5155
assertThat(parameter.isTemporalParameter()).isFalse();
5256
}
5357

54-
interface SampleRepository {
58+
interface SampleRepository extends Repository<String, String> {
5559

5660
void foo(@Temporal(TIMESTAMP) Date date, String firstname);
5761
}

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@
2020
import static org.mockito.Mockito.*;
2121

2222
import io.vavr.control.Try;
23+
import jakarta.persistence.EntityManager;
24+
import jakarta.persistence.Query;
25+
import jakarta.persistence.TypedQuery;
2326

2427
import java.lang.reflect.Method;
2528
import java.util.Arrays;
2629
import java.util.Collections;
2730
import java.util.Optional;
2831

29-
import jakarta.persistence.EntityManager;
30-
import jakarta.persistence.Query;
31-
import jakarta.persistence.TypedQuery;
32-
3332
import org.junit.jupiter.api.BeforeEach;
3433
import org.junit.jupiter.api.Test;
3534
import org.junit.jupiter.api.extension.ExtendWith;
@@ -48,6 +47,7 @@
4847
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
4948
import org.springframework.data.repository.Repository;
5049
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
50+
import org.springframework.data.repository.query.ParametersSource;
5151

5252
/**
5353
* Unit test for {@link JpaQueryExecution}.
@@ -177,7 +177,8 @@ void modifyingExecutionRejectsNonIntegerOrVoidReturnType() {
177177
@Test // DATAJPA-124, DATAJPA-912
178178
void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Exception {
179179

180-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
180+
JpaParameters parameters = new JpaParameters(
181+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
181182
when(jpaQuery.createCountQuery(Mockito.any())).thenReturn(countQuery);
182183
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
183184
when(countQuery.getResultList()).thenReturn(Arrays.asList(20L));
@@ -193,7 +194,8 @@ void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Exception {
193194
@Test // DATAJPA-477, DATAJPA-912
194195
void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() throws Exception {
195196

196-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
197+
JpaParameters parameters = new JpaParameters(
198+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
197199
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
198200
when(query.getResultList()).thenReturn(Arrays.asList(0L));
199201

@@ -208,7 +210,8 @@ void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() throws
208210
@Test // DATAJPA-912
209211
void pagedExecutionShouldUseCountFromResultIfOffsetIsZeroAndResultsWithinPageSize() throws Exception {
210212

211-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
213+
JpaParameters parameters = new JpaParameters(
214+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
212215
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
213216
when(query.getResultList()).thenReturn(Arrays.asList(new Object(), new Object(), new Object(), new Object()));
214217

@@ -222,7 +225,8 @@ void pagedExecutionShouldUseCountFromResultIfOffsetIsZeroAndResultsWithinPageSiz
222225
@Test // DATAJPA-912
223226
void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() throws Exception {
224227

225-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
228+
JpaParameters parameters = new JpaParameters(
229+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
226230
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
227231
when(query.getResultList()).thenReturn(Arrays.asList(new Object(), new Object(), new Object(), new Object()));
228232

@@ -234,10 +238,10 @@ void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize()
234238
}
235239

236240
@Test // DATAJPA-912
237-
void pagedExecutionShouldUseRequestCountFromResultWithOffsetAndResultsHitLowerPageSizeBounds()
238-
throws Exception {
241+
void pagedExecutionShouldUseRequestCountFromResultWithOffsetAndResultsHitLowerPageSizeBounds() throws Exception {
239242

240-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
243+
JpaParameters parameters = new JpaParameters(
244+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
241245
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
242246
when(query.getResultList()).thenReturn(Collections.emptyList());
243247
when(jpaQuery.createCountQuery(Mockito.any())).thenReturn(query);
@@ -251,10 +255,10 @@ void pagedExecutionShouldUseRequestCountFromResultWithOffsetAndResultsHitLowerPa
251255
}
252256

253257
@Test // DATAJPA-912
254-
void pagedExecutionShouldUseRequestCountFromResultWithOffsetAndResultsHitUpperPageSizeBounds()
255-
throws Exception {
258+
void pagedExecutionShouldUseRequestCountFromResultWithOffsetAndResultsHitUpperPageSizeBounds() throws Exception {
256259

257-
JpaParameters parameters = new JpaParameters(getClass().getMethod("sampleMethod", Pageable.class));
260+
JpaParameters parameters = new JpaParameters(
261+
ParametersSource.of(getClass().getMethod("sampleMethod", Pageable.class)));
258262
when(jpaQuery.createQuery(Mockito.any())).thenReturn(query);
259263
when(query.getResultList()).thenReturn(Arrays.asList(new Object(), new Object(), new Object(), new Object()));
260264
when(jpaQuery.createCountQuery(Mockito.any())).thenReturn(query);

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
* @author Mark Paluch
6969
* @author Erik Pellizzon
7070
*/
71+
@SuppressWarnings({"rawtypes", "unchecked"})
7172
@ExtendWith(MockitoExtension.class)
7273
@MockitoSettings(strictness = Strictness.LENIENT)
7374
class JpaQueryMethodUnitTests {
@@ -156,6 +157,8 @@ void returnsQueryIfAvailable() throws Exception {
156157
void rejectsInvalidReturntypeOnPagebleFinder() {
157158

158159
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
160+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
161+
when(metadata.getRepositoryInterface()).thenReturn((Class) InvalidRepository.class);
159162

160163
assertThatIllegalStateException()
161164
.isThrownBy(() -> new JpaQueryMethod(invalidReturnType, metadata, factory, extractor));
@@ -165,6 +168,8 @@ void rejectsInvalidReturntypeOnPagebleFinder() {
165168
void rejectsPageableAndSortInFinderMethod() {
166169

167170
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
171+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
172+
when(metadata.getRepositoryInterface()).thenReturn((Class) InvalidRepository.class);
168173

169174
assertThatIllegalStateException()
170175
.isThrownBy(() -> new JpaQueryMethod(pageableAndSort, metadata, factory, extractor));
@@ -318,8 +323,10 @@ void detectsLockAndQueryHintsOnIfUsedAsMetaAnnotation() throws Exception {
318323
@Test // DATAJPA-466
319324
void shouldStoreJpa21FetchGraphInformationAsHint() {
320325

321-
doReturn(User.class).when(metadata).getDomainType();
322-
doReturn(User.class).when(metadata).getReturnedDomainClass(queryMethodWithCustomEntityFetchGraph);
326+
when(metadata.getDomainType()).thenReturn((Class) User.class);
327+
when(metadata.getReturnedDomainClass(queryMethodWithCustomEntityFetchGraph)).thenReturn((Class) User.class);
328+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
329+
when(metadata.getRepositoryInterface()).thenReturn((Class) InvalidRepository.class);
323330

324331
JpaQueryMethod method = new JpaQueryMethod(queryMethodWithCustomEntityFetchGraph, metadata, factory, extractor);
325332

@@ -331,8 +338,10 @@ void shouldStoreJpa21FetchGraphInformationAsHint() {
331338
@Test // DATAJPA-612
332339
void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethod() throws Exception {
333340

334-
doReturn(User.class).when(metadata).getDomainType();
335-
doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any());
341+
when(metadata.getDomainType()).thenReturn((Class) User.class);
342+
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
343+
when(metadata.getReturnedDomainClass(queryMethodWithCustomEntityFetchGraph)).thenReturn((Class) User.class);
344+
when(metadata.getRepositoryInterface()).thenReturn((Class) JpaRepositoryOverride.class);
336345

337346
JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findAll"), metadata, factory,
338347
extractor);
@@ -345,8 +354,10 @@ void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethod() thro
345354
@Test // DATAJPA-689
346355
void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethodFindOne() throws Exception {
347356

348-
doReturn(User.class).when(metadata).getDomainType();
349-
doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any());
357+
when(metadata.getDomainType()).thenReturn((Class) User.class);
358+
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
359+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
360+
when(metadata.getRepositoryInterface()).thenReturn((Class) InvalidRepository.class);
350361

351362
JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne", Integer.class),
352363
metadata, factory, extractor);
@@ -362,8 +373,10 @@ void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethodFindOne
362373
@Test
363374
void shouldFindEntityGraphAnnotationOnQueryMethodGetOneByWithDerivedName() throws Exception {
364375

365-
doReturn(User.class).when(metadata).getDomainType();
366-
doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any());
376+
when(metadata.getDomainType()).thenReturn((Class) User.class);
377+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
378+
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
379+
when(metadata.getRepositoryInterface()).thenReturn((Class) JpaRepositoryOverride.class);
367380

368381
JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("getOneById", Integer.class),
369382
metadata, factory, extractor);
@@ -473,8 +486,10 @@ void usesAliasedValueForQueryNativeQuery() throws Exception {
473486
@Test // DATAJPA-871
474487
void usesAliasedValueForEntityGraph() throws Exception {
475488

476-
doReturn(User.class).when(metadata).getDomainType();
477-
doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any());
489+
when(metadata.getDomainType()).thenReturn((Class) User.class);
490+
when(metadata.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(User.class));
491+
when(metadata.getReturnedDomainClass(any())).thenReturn((Class) User.class);
492+
when(metadata.getRepositoryInterface()).thenReturn((Class) JpaRepositoryOverride.class);
478493

479494
JpaQueryMethod method = new JpaQueryMethod(
480495
JpaRepositoryOverride.class.getMethod("getOneWithCustomEntityGraphAnnotation"), metadata, factory, extractor);

0 commit comments

Comments
 (0)