Skip to content

Commit f21e91c

Browse files
mp911deschauder
authored andcommitted
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.
1 parent 8ff8863 commit f21e91c

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<eclipselink>2.7.5</eclipselink>
2525
<hibernate>5.4.8.Final</hibernate>
2626
<mockito>2.19.1</mockito>
27+
<vavr>0.10.3</vavr>
2728
<hibernate.groupId>org.hibernate</hibernate.groupId>
2829
<springdata.commons>2.4.3-SNAPSHOT</springdata.commons>
2930

@@ -204,6 +205,13 @@
204205
<optional>true</optional>
205206
</dependency>
206207

208+
<dependency>
209+
<groupId>io.vavr</groupId>
210+
<artifactId>vavr</artifactId>
211+
<version>${vavr}</version>
212+
<scope>test</scope>
213+
</dependency>
214+
207215
<!-- Persistence providers -->
208216

209217
<dependency>

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

+18-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
import org.springframework.data.repository.query.Parameter;
4141
import org.springframework.data.repository.query.Parameters;
4242
import org.springframework.data.repository.query.QueryMethod;
43+
import org.springframework.data.repository.util.QueryExecutionConverters;
4344
import org.springframework.data.util.Lazy;
45+
import org.springframework.data.util.TypeInformation;
4446
import org.springframework.lang.Nullable;
4547
import org.springframework.util.Assert;
4648
import org.springframework.util.StringUtils;
@@ -79,6 +81,7 @@ public class JpaQueryMethod extends QueryMethod {
7981

8082
private final QueryExtractor extractor;
8183
private final Method method;
84+
private final Class<?> returnType;
8285

8386
private @Nullable StoredProcedureAttributes storedProcedureAttributes;
8487
private final Lazy<LockModeType> lockModeType;
@@ -107,6 +110,7 @@ protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionF
107110
Assert.notNull(extractor, "Query extractor must not be null!");
108111

109112
this.method = method;
113+
this.returnType = potentiallyUnwrapReturnTypeFor(metadata, method);
110114
this.extractor = extractor;
111115
this.lockModeType = Lazy
112116
.of(() -> (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class)) //
@@ -126,8 +130,7 @@ protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionF
126130
return new JpaEntityGraph(entityGraph, getNamedQueryName());
127131
});
128132
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
129-
this.isCollectionQuery = Lazy
130-
.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType()));
133+
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
131134
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
132135
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
133136

@@ -136,6 +139,18 @@ protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionF
136139
assertParameterNamesInAnnotatedQuery();
137140
}
138141

142+
private static Class<?> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
143+
144+
TypeInformation<?> returnType = metadata.getReturnType(method);
145+
146+
while (QueryExecutionConverters.supports(returnType.getType())
147+
|| QueryExecutionConverters.supportsUnwrapping(returnType.getType())) {
148+
returnType = returnType.getRequiredComponentType();
149+
}
150+
151+
return returnType.getType();
152+
}
153+
139154
private void assertParameterNamesInAnnotatedQuery() {
140155

141156
String annotatedQuery = getAnnotatedQuery();
@@ -243,7 +258,7 @@ QueryExtractor getQueryExtractor() {
243258
* @return
244259
*/
245260
Class<?> getReturnType() {
246-
return method.getReturnType();
261+
return returnType;
247262
}
248263

249264
/**

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

+29
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import static org.mockito.ArgumentMatchers.*;
2020
import static org.mockito.Mockito.*;
2121

22+
import io.vavr.control.Try;
23+
24+
import java.lang.reflect.Method;
2225
import java.util.Arrays;
2326
import java.util.Collections;
2427
import java.util.Optional;
@@ -38,8 +41,13 @@
3841

3942
import org.springframework.data.domain.PageRequest;
4043
import org.springframework.data.domain.Pageable;
44+
import org.springframework.data.jpa.provider.QueryExtractor;
45+
import org.springframework.data.jpa.repository.Modifying;
4146
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
4247
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
48+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
49+
import org.springframework.data.repository.Repository;
50+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
4351

4452
/**
4553
* Unit test for {@link JpaQueryExecution}.
@@ -85,6 +93,27 @@ void rejectsNullBinder() {
8593
assertThatIllegalArgumentException().isThrownBy(() -> new StubQueryExecution().execute(jpaQuery, null));
8694
}
8795

96+
@Test // DATAJPA-1827
97+
void supportsModifyingResultsUsingWrappers() throws Exception {
98+
99+
Method method = VavrRepository.class.getMethod("updateUsingVavrMethod");
100+
DefaultRepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(VavrRepository.class);
101+
JpaQueryMethod queryMethod = new JpaQueryMethod(method, repositoryMetadata, new SpelAwareProxyProjectionFactory(),
102+
mock(QueryExtractor.class));
103+
104+
new JpaQueryExecution.ModifyingExecution(queryMethod, mock(EntityManager.class));
105+
106+
assertThat(queryMethod.isModifyingQuery()).isTrue();
107+
}
108+
109+
interface VavrRepository extends Repository<String, String> {
110+
111+
// Wrapped outcome allowed
112+
@org.springframework.data.jpa.repository.Query("update Credential d set d.enabled = false where d.enabled = true")
113+
@Modifying
114+
Try<Integer> updateUsingVavrMethod();
115+
}
116+
88117
@Test
89118
void transformsNoResultExceptionToNull() {
90119

0 commit comments

Comments
 (0)