DATAJPA-1827 - Consider wrapper types for Modifying JPA Query Execution.

We now consider wrapper types (nullable types, Vavr/Javaslang/Futures) as potential wrappers and inspect the component type of each wrapper to determine the actual method return type.

Original pull request: #438.
This commit is contained in:
Mark Paluch
2020-12-08 14:18:09 +01:00
committed by Jens Schauder
parent 8ff88631e0
commit f21e91c5e8
3 changed files with 55 additions and 3 deletions

View File

@@ -24,6 +24,7 @@
<eclipselink>2.7.5</eclipselink>
<hibernate>5.4.8.Final</hibernate>
<mockito>2.19.1</mockito>
<vavr>0.10.3</vavr>
<hibernate.groupId>org.hibernate</hibernate.groupId>
<springdata.commons>2.4.3-SNAPSHOT</springdata.commons>
@@ -204,6 +205,13 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>${vavr}</version>
<scope>test</scope>
</dependency>
<!-- Persistence providers -->
<dependency>

View File

@@ -40,7 +40,9 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -79,6 +81,7 @@ public class JpaQueryMethod extends QueryMethod {
private final QueryExtractor extractor;
private final Method method;
private final Class<?> returnType;
private @Nullable StoredProcedureAttributes storedProcedureAttributes;
private final Lazy<LockModeType> lockModeType;
@@ -107,6 +110,7 @@ public class JpaQueryMethod extends QueryMethod {
Assert.notNull(extractor, "Query extractor must not be null!");
this.method = method;
this.returnType = potentiallyUnwrapReturnTypeFor(metadata, method);
this.extractor = extractor;
this.lockModeType = Lazy
.of(() -> (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class)) //
@@ -126,8 +130,7 @@ public class JpaQueryMethod extends QueryMethod {
return new JpaEntityGraph(entityGraph, getNamedQueryName());
});
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
this.isCollectionQuery = Lazy
.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType()));
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
@@ -136,6 +139,18 @@ public class JpaQueryMethod extends QueryMethod {
assertParameterNamesInAnnotatedQuery();
}
private static Class<?> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
TypeInformation<?> returnType = metadata.getReturnType(method);
while (QueryExecutionConverters.supports(returnType.getType())
|| QueryExecutionConverters.supportsUnwrapping(returnType.getType())) {
returnType = returnType.getRequiredComponentType();
}
return returnType.getType();
}
private void assertParameterNamesInAnnotatedQuery() {
String annotatedQuery = getAnnotatedQuery();
@@ -243,7 +258,7 @@ public class JpaQueryMethod extends QueryMethod {
* @return
*/
Class<?> getReturnType() {
return method.getReturnType();
return returnType;
}
/**

View File

@@ -19,6 +19,9 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import io.vavr.control.Try;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
@@ -38,8 +41,13 @@ import org.mockito.quality.Strictness;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
/**
* Unit test for {@link JpaQueryExecution}.
@@ -85,6 +93,27 @@ class JpaQueryExecutionUnitTests {
assertThatIllegalArgumentException().isThrownBy(() -> new StubQueryExecution().execute(jpaQuery, null));
}
@Test // DATAJPA-1827
void supportsModifyingResultsUsingWrappers() throws Exception {
Method method = VavrRepository.class.getMethod("updateUsingVavrMethod");
DefaultRepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(VavrRepository.class);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, repositoryMetadata, new SpelAwareProxyProjectionFactory(),
mock(QueryExtractor.class));
new JpaQueryExecution.ModifyingExecution(queryMethod, mock(EntityManager.class));
assertThat(queryMethod.isModifyingQuery()).isTrue();
}
interface VavrRepository extends Repository<String, String> {
// Wrapped outcome allowed
@org.springframework.data.jpa.repository.Query("update Credential d set d.enabled = false where d.enabled = true")
@Modifying
Try<Integer> updateUsingVavrMethod();
}
@Test
void transformsNoResultExceptionToNull() {