Skip to content

Commit 5f84e2a

Browse files
committed
Consider Tuples in Keyset extraction.
We now consider Tuple values when the query uses tuples for e.g. interface projections when extracting keyset values. Closes #3077
1 parent d6bcdf3 commit 5f84e2a

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import jakarta.persistence.IdClass;
1919
import jakarta.persistence.PersistenceUnitUtil;
20+
import jakarta.persistence.Tuple;
2021
import jakarta.persistence.metamodel.Attribute;
2122
import jakarta.persistence.metamodel.EntityType;
2223
import jakarta.persistence.metamodel.IdentifiableType;
@@ -34,6 +35,7 @@
3435
import java.util.Map;
3536
import java.util.Optional;
3637
import java.util.Set;
38+
import java.util.function.Function;
3739

3840
import org.springframework.beans.BeanWrapper;
3941
import org.springframework.core.annotation.AnnotationUtils;
@@ -154,6 +156,11 @@ public ID getId(T entity) {
154156

155157
// If it's a simple type, then immediately delegate to the provider
156158
if (idMetadata.hasSimpleId()) {
159+
160+
if (entity instanceof Tuple t) {
161+
return (ID) t.get(idMetadata.getSimpleIdAttribute().getName());
162+
}
163+
157164
return (ID) persistenceUnitUtil.getIdentifier(entity);
158165
}
159166

@@ -225,27 +232,38 @@ public boolean isNew(T entity) {
225232
@Override
226233
public Map<String, Object> getKeyset(Iterable<String> propertyPaths, T entity) {
227234

228-
// TODO: Proxy handling requires more elaborate refactoring, see
229-
// https://github.com/spring-projects/spring-data-jpa/issues/2784
230-
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
235+
Function<String, Object> getter = getPropertyValueFunction(entity);
231236

232237
Map<String, Object> keyset = new LinkedHashMap<>();
233238

234239
if (hasCompositeId()) {
235240
for (String idAttributeName : getIdAttributeNames()) {
236-
keyset.put(idAttributeName, entityWrapper.getPropertyValue(idAttributeName));
241+
keyset.put(idAttributeName, getter.apply(idAttributeName));
237242
}
238243
} else {
239244
keyset.put(getIdAttribute().getName(), getId(entity));
240245
}
241246

242247
for (String propertyPath : propertyPaths) {
243-
keyset.put(propertyPath, entityWrapper.getPropertyValue(propertyPath));
248+
keyset.put(propertyPath, getter.apply(propertyPath));
244249
}
245250

246251
return keyset;
247252
}
248253

254+
private Function<String, Object> getPropertyValueFunction(Object entity) {
255+
256+
if (entity instanceof Tuple t) {
257+
return t::get;
258+
}
259+
260+
// TODO: Proxy handling requires more elaborate refactoring, see
261+
// https://github.com/spring-projects/spring-data-jpa/issues/2784
262+
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
263+
264+
return entityWrapper::getPropertyValue;
265+
}
266+
249267
/**
250268
* Simple value object to encapsulate id specific metadata.
251269
*

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

+16
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@
3232
import org.springframework.data.domain.Page;
3333
import org.springframework.data.domain.PageRequest;
3434
import org.springframework.data.domain.Pageable;
35+
import org.springframework.data.domain.ScrollPosition;
3536
import org.springframework.data.domain.Slice;
3637
import org.springframework.data.domain.Sort;
38+
import org.springframework.data.domain.Window;
3739
import org.springframework.data.jpa.domain.sample.Role;
3840
import org.springframework.data.jpa.domain.sample.User;
3941
import org.springframework.data.jpa.provider.PersistenceProvider;
4042
import org.springframework.data.jpa.repository.sample.RoleRepository;
4143
import org.springframework.data.jpa.repository.sample.UserRepository;
4244
import org.springframework.data.jpa.repository.sample.UserRepository.IdOnly;
45+
import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly;
4346
import org.springframework.data.jpa.repository.sample.UserRepository.RolesAndFirstname;
4447
import org.springframework.data.repository.query.QueryLookupStrategy;
4548
import org.springframework.test.context.ContextConfiguration;
@@ -221,6 +224,19 @@ void executesQueryToSliceWithUnpaged() {
221224
assertThat(slice.hasNext()).isFalse();
222225
}
223226

227+
@Test // GH-3077
228+
void shouldProjectWithKeysetScrolling() {
229+
230+
Window<NameOnly> first = userRepository.findTop1ByLastnameOrderByFirstname(ScrollPosition.keyset(), //
231+
"Matthews");
232+
233+
Window<NameOnly> next = userRepository.findTop1ByLastnameOrderByFirstname(first.positionAt(0), //
234+
"Matthews");
235+
236+
assertThat(first.getContent()).extracting(NameOnly::getFirstname).containsOnly(dave.getFirstname());
237+
assertThat(next.getContent()).extracting(NameOnly::getFirstname).containsOnly(oliver.getFirstname());
238+
}
239+
224240
@Test // DATAJPA-830
225241
void executesMethodWithNotContainingOnStringCorrectly() {
226242

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

+2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ Window<User> findTop3ByFirstnameStartingWithOrderByFirstnameAscEmailAddressAsc(S
226226

227227
Page<User> findByLastnameIgnoringCase(Pageable pageable, String lastname);
228228

229+
Window<NameOnly> findTop1ByLastnameOrderByFirstname(ScrollPosition scrollPosition, String lastname);
230+
229231
List<User> findByLastnameIgnoringCaseLike(String lastname);
230232

231233
List<User> findByLastnameAndFirstnameAllIgnoringCase(String lastname, String firstname);

0 commit comments

Comments
 (0)