diff --git a/pom.xml b/pom.xml index 35c452d34..680c152db 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.3.7.BUILD-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 3c0a5ba44..0078ca87c 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; @@ -36,8 +39,13 @@ import org.mockito.junit.MockitoJUnitRunner; 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}. @@ -81,6 +89,27 @@ public class JpaQueryExecutionUnitTests { 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 public void transformsNoResultExceptionToNull() {