diff --git a/pom.xml b/pom.xml index 119e2f15c..9183c7a3e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 2.7.5 5.4.8.Final 2.19.1 + 0.10.3 org.hibernate 2.4.3-SNAPSHOT @@ -204,6 +205,13 @@ true + + io.vavr + vavr + ${vavr} + test + + diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index d26c7759f..d1fd62ed3 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -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; @@ -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; } /** diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java index bb7d3989f..715f4c7ea 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryExecutionUnitTests.java @@ -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 { + + // Wrapped outcome allowed + @org.springframework.data.jpa.repository.Query("update Credential d set d.enabled = false where d.enabled = true") + @Modifying + Try updateUsingVavrMethod(); + } + @Test void transformsNoResultExceptionToNull() {