Skip to content

Commit 697dbe3

Browse files
committed
Add support for Streamable wrappers.
We now support conversion to wrapper types implementing Streamable. See #4070
1 parent 285fba6 commit 697dbe3

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaCodeBlocks.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
import org.springframework.core.MethodParameter;
3131
import org.springframework.core.annotation.MergedAnnotation;
32+
import org.springframework.core.convert.TypeDescriptor;
33+
import org.springframework.core.convert.support.DefaultConversionService;
3234
import org.springframework.dao.IncorrectResultSizeDataAccessException;
3335
import org.springframework.data.domain.Pageable;
3436
import org.springframework.data.domain.Score;
@@ -707,6 +709,12 @@ public CodeBlock build() {
707709

708710
if (isStreamable(methodReturn)) {
709711
builder.addStatement("return ($1T) $1T.of($2L)", Streamable.class, context.localVariable("resultList"));
712+
} else if (isStreamableWrapper(methodReturn) && canConvert(Streamable.class, methodReturn)) {
713+
714+
builder.addStatement(
715+
"return ($1T) $2T.getSharedInstance().convert($3T.of($4L), $5T.valueOf($3T.class), $5T.valueOf($1T.class))",
716+
methodReturn.toClass(), DefaultConversionService.class, Streamable.class,
717+
context.localVariable("resultList"), TypeDescriptor.class);
710718
} else {
711719
builder.addStatement("return ($T) $L", List.class, context.localVariable("resultList"));
712720
}
@@ -775,10 +783,14 @@ public CodeBlock build() {
775783
} else {
776784

777785
if (queryMethod.isCollectionQuery()) {
778-
779786
if (isStreamable(methodReturn)) {
780787
builder.addStatement("return ($T) $T.of($L.getResultList())", methodReturn.getTypeName(),
781788
Streamable.class, queryVariableName);
789+
} else if (isStreamableWrapper(methodReturn) && canConvert(Streamable.class, methodReturn)) {
790+
builder.addStatement(
791+
"return ($1T) $2T.getSharedInstance().convert($3T.of($4L.getResultList()), $5T.valueOf($3T.class), $5T.valueOf($1T.class))",
792+
methodReturn.toClass(), DefaultConversionService.class, Streamable.class, queryVariableName,
793+
TypeDescriptor.class);
782794
} else {
783795
builder.addStatement("return ($T) $L.getResultList()", methodReturn.getTypeName(), queryVariableName);
784796
}
@@ -811,10 +823,18 @@ public CodeBlock build() {
811823
return builder.build();
812824
}
813825

826+
private boolean canConvert(Class<?> from, MethodReturn methodReturn) {
827+
return DefaultConversionService.getSharedInstance().canConvert(from, methodReturn.toClass());
828+
}
829+
814830
private static boolean isStreamable(MethodReturn methodReturn) {
815831
return methodReturn.toClass().equals(Streamable.class);
816832
}
817833

834+
private static boolean isStreamableWrapper(MethodReturn methodReturn) {
835+
return !isStreamable(methodReturn) && Streamable.class.isAssignableFrom(methodReturn.toClass());
836+
}
837+
818838
public static boolean returnsModifying(Class<?> returnType) {
819839

820840
return returnType == int.class || returnType == long.class || returnType == Integer.class

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder;
7575
import org.springframework.data.jpa.repository.sample.UserRepository;
7676
import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly;
77+
import org.springframework.data.jpa.repository.sample.Users;
7778
import org.springframework.data.jpa.util.DisabledOnHibernate;
7879
import org.springframework.data.util.Streamable;
7980
import org.springframework.test.context.ContextConfiguration;
@@ -2157,6 +2158,15 @@ void supportsStreamableForPageableMethod() {
21572158
assertThat(users).hasSize(2);
21582159
}
21592160

2161+
@Test // GH-4070
2162+
void supportsStreamableWrapper() {
2163+
2164+
flushTestUsers();
2165+
2166+
Users users = repository.readUsersByFirstnameNotNull(PageRequest.of(0, 2));
2167+
assertThat(users).hasSize(2);
2168+
}
2169+
21602170
@Test // DATAJPA-218
21612171
void findAllByExample() {
21622172

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ List<User> findUsersByFirstnameForSpELExpressionWithParameterIndexOnlyWithEntity
536536

537537
Streamable<User> readStreamableAllByFirstnameNotNull(Pageable pageable);
538538

539+
Users readUsersByFirstnameNotNull(Pageable pageable);
540+
539541
// DATAJPA-830
540542
List<User> findByLastnameNotContaining(String part);
541543

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.sample;
17+
18+
import java.util.Iterator;
19+
20+
import org.springframework.data.jpa.domain.sample.User;
21+
import org.springframework.data.util.Streamable;
22+
23+
/**
24+
* @author Mark Paluch
25+
*/
26+
public class Users implements Streamable<User> {
27+
28+
private final Streamable<User> users;
29+
30+
private Users(Streamable<User> users) {
31+
this.users = users;
32+
}
33+
34+
public static Users of(Streamable<User> users) {
35+
return new Users(users);
36+
}
37+
38+
@Override
39+
public Iterator<User> iterator() {
40+
return users.iterator();
41+
}
42+
43+
}

0 commit comments

Comments
 (0)