Query method parameters are now aware of aggregate reference type.

To support the binding of Class parameters to queries for declared query methods, we have to be able to differentiate them from Class parameters that are supposed to represent projections. We can do that by relating the declared Class' element type to the aggregate root type as a Class typed to that or any subtype of it will never trigger a projection by definition.

So far the Parameter(s) abstraction was solely created from a query method's Method. We now changed that for QueryMethod to forward the aggregate type detected on the RepositoryMetadata and consider it during the detection of dynamic projection parameters.

As a mitigating measure, we now also support @Param on Class-typed parameters to explicitly mark them for query binding. This is primarily to be able to add this support to the 2.7

The changes are built in a way that modules extending that mechanism will continue to work as is but see deprecation warnings on methods and constructors involved. Adapting extending code to the new APIs will automatically enable the support for bindable Class parameters on query methods.

Fixes #2770.
Original pull request: #2771
This commit is contained in:
Oliver Drotbohm
2023-01-31 18:04:12 +01:00
committed by Mark Paluch
parent 9bc6d2b6df
commit 16e7e7c79b
5 changed files with 136 additions and 27 deletions

View File

@@ -23,15 +23,19 @@ import java.util.Optional;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.springframework.core.MethodParameter;
import org.springframework.data.repository.query.ParametersUnitTests.User;
import org.springframework.data.util.TypeInformation;
/**
* Unit tests for {@link Parameter}.
*
* @author Jens Schauder
*/
class KParameterUnitTests {
class ParameterUnitTests {
@Test // DATAJPA-1185
void classParameterWithSameTypeParameterAsReturnedListIsDynamicProjectionParameter() throws Exception {
@@ -57,6 +61,28 @@ class KParameterUnitTests {
assertThat(parameter.isDynamicProjectionParameter()).isTrue();
}
@TestFactory // #2770
Stream<DynamicTest> doesNotConsiderClassParametersDynamicProjectionOnes() {
var methods = Stream.of( //
"genericReturnNonDynamicBind", //
"staticReturnNonDynamicBindWildcard", //
"staticReturnNonDynamicBindWildcardExtends");
return DynamicTest.stream(methods, it -> it, it -> {
assertThat(new Parameter(getMethodParameter(it), TypeInformation.of(User.class))
.isDynamicProjectionParameter()).isFalse();
});
}
@Test // #2770
void doesNotConsiderAtParamAnnotatedClassParameterDynamicProjectionOne() throws Exception {
var parameter = new Parameter(getMethodParameter("atParamOnClass"));
assertThat(parameter.isDynamicProjectionParameter()).isFalse();
}
@NotNull
private MethodParameter getMethodParameter(String methodName) throws NoSuchMethodException {
return new MethodParameter(this.getClass().getDeclaredMethod(methodName, Class.class), 0);
@@ -73,4 +99,20 @@ class KParameterUnitTests {
<T> Optional<T> dynamicProjectionWithOptional(Class<T> type) {
return Optional.empty();
}
<T> T genericReturnNonDynamicBind(Class<? extends User> one) {
return null;
}
User staticReturnNonDynamicBindWildcard(Class<?> two) {
return null;
}
User staticReturnNonDynamicBindWildcardExtends(Class<? extends User> one) {
return null;
}
<T> T atParamOnClass(@Param("type") Class<T> type) {
return null;
}
}