Skip to content

Commit 03ff012

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 6b85cb4 commit 03ff012

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<eclipselink>2.6.8</eclipselink>
2525
<hibernate>5.2.17.Final</hibernate>
2626
<mockito>2.19.1</mockito>
27+
<vavr>0.10.3</vavr>
2728
<hibernate.groupId>org.hibernate</hibernate.groupId>
2829
<jpa>2.0.0</jpa>
2930
<springdata.commons>2.2.13.BUILD-SNAPSHOT</springdata.commons>
@@ -288,6 +289,13 @@
288289
<optional>true</optional>
289290
</dependency>
290291

292+
<dependency>
293+
<groupId>io.vavr</groupId>
294+
<artifactId>vavr</artifactId>
295+
<version>${vavr}</version>
296+
<scope>test</scope>
297+
</dependency>
298+
291299
<!-- Persistence providers -->
292300

293301
<dependency>

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

Lines changed: 18 additions & 3 deletions
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;
@@ -78,6 +80,7 @@ public class JpaQueryMethod extends QueryMethod {
7880

7981
private final QueryExtractor extractor;
8082
private final Method method;
83+
private final Class<?> returnType;
8184

8285
private @Nullable StoredProcedureAttributes storedProcedureAttributes;
8386
private final Lazy<LockModeType> lockModeType;
@@ -106,6 +109,7 @@ public JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFact
106109
Assert.notNull(extractor, "Query extractor must not be null!");
107110

108111
this.method = method;
112+
this.returnType = potentiallyUnwrapReturnTypeFor(metadata, method);
109113
this.extractor = extractor;
110114
this.lockModeType = Lazy
111115
.of(() -> (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class)) //
@@ -125,8 +129,7 @@ public JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFact
125129
return new JpaEntityGraph(entityGraph, getNamedQueryName());
126130
});
127131
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
128-
this.isCollectionQuery = Lazy
129-
.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType()));
132+
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
130133
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
131134
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
132135

@@ -135,6 +138,18 @@ public JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFact
135138
assertParameterNamesInAnnotatedQuery();
136139
}
137140

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

140155
String annotatedQuery = getAnnotatedQuery();
@@ -242,7 +257,7 @@ QueryExtractor getQueryExtractor() {
242257
* @return
243258
*/
244259
Class<?> getReturnType() {
245-
return method.getReturnType();
260+
return returnType;
246261
}
247262

248263
/**

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

Lines changed: 29 additions & 0 deletions
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;
@@ -36,8 +39,13 @@
3639

3740
import org.springframework.data.domain.PageRequest;
3841
import org.springframework.data.domain.Pageable;
42+
import org.springframework.data.jpa.provider.QueryExtractor;
43+
import org.springframework.data.jpa.repository.Modifying;
3944
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
4045
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
46+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
47+
import org.springframework.data.repository.Repository;
48+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
4149

4250
/**
4351
* Unit test for {@link JpaQueryExecution}.
@@ -81,6 +89,27 @@ public void rejectsNullBinder() throws Exception {
8189
new StubQueryExecution().execute(jpaQuery, null);
8290
}
8391

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

0 commit comments

Comments
 (0)