From 5f84e2aa636d836e32439f2fc87102d60b3b23d1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 20 Jul 2023 11:57:48 +0200 Subject: [PATCH] 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 --- .../JpaMetamodelEntityInformation.java | 28 +++++++++++++++---- .../repository/UserRepositoryFinderTests.java | 16 +++++++++++ .../jpa/repository/sample/UserRepository.java | 2 ++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index dbf7285a0..362bf21c8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.support; import jakarta.persistence.IdClass; import jakarta.persistence.PersistenceUnitUtil; +import jakarta.persistence.Tuple; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.IdentifiableType; @@ -34,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import org.springframework.beans.BeanWrapper; import org.springframework.core.annotation.AnnotationUtils; @@ -154,6 +156,11 @@ public class JpaMetamodelEntityInformation extends JpaEntityInformationSu // If it's a simple type, then immediately delegate to the provider if (idMetadata.hasSimpleId()) { + + if (entity instanceof Tuple t) { + return (ID) t.get(idMetadata.getSimpleIdAttribute().getName()); + } + return (ID) persistenceUnitUtil.getIdentifier(entity); } @@ -225,27 +232,38 @@ public class JpaMetamodelEntityInformation extends JpaEntityInformationSu @Override public Map getKeyset(Iterable propertyPaths, T entity) { - // TODO: Proxy handling requires more elaborate refactoring, see - // https://github.com/spring-projects/spring-data-jpa/issues/2784 - BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity); + Function getter = getPropertyValueFunction(entity); Map keyset = new LinkedHashMap<>(); if (hasCompositeId()) { for (String idAttributeName : getIdAttributeNames()) { - keyset.put(idAttributeName, entityWrapper.getPropertyValue(idAttributeName)); + keyset.put(idAttributeName, getter.apply(idAttributeName)); } } else { keyset.put(getIdAttribute().getName(), getId(entity)); } for (String propertyPath : propertyPaths) { - keyset.put(propertyPath, entityWrapper.getPropertyValue(propertyPath)); + keyset.put(propertyPath, getter.apply(propertyPath)); } return keyset; } + private Function getPropertyValueFunction(Object entity) { + + if (entity instanceof Tuple t) { + return t::get; + } + + // TODO: Proxy handling requires more elaborate refactoring, see + // https://github.com/spring-projects/spring-data-jpa/issues/2784 + BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity); + + return entityWrapper::getPropertyValue; + } + /** * Simple value object to encapsulate id specific metadata. * diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java index 5ded8dd6b..f095c579a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryFinderTests.java @@ -32,14 +32,17 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Window; import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.sample.RoleRepository; import org.springframework.data.jpa.repository.sample.UserRepository; import org.springframework.data.jpa.repository.sample.UserRepository.IdOnly; +import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly; import org.springframework.data.jpa.repository.sample.UserRepository.RolesAndFirstname; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.test.context.ContextConfiguration; @@ -221,6 +224,19 @@ class UserRepositoryFinderTests { assertThat(slice.hasNext()).isFalse(); } + @Test // GH-3077 + void shouldProjectWithKeysetScrolling() { + + Window first = userRepository.findTop1ByLastnameOrderByFirstname(ScrollPosition.keyset(), // + "Matthews"); + + Window next = userRepository.findTop1ByLastnameOrderByFirstname(first.positionAt(0), // + "Matthews"); + + assertThat(first.getContent()).extracting(NameOnly::getFirstname).containsOnly(dave.getFirstname()); + assertThat(next.getContent()).extracting(NameOnly::getFirstname).containsOnly(oliver.getFirstname()); + } + @Test // DATAJPA-830 void executesMethodWithNotContainingOnStringCorrectly() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index fad09d8a2..b5182b078 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -226,6 +226,8 @@ public interface UserRepository extends JpaRepository, JpaSpecifi Page findByLastnameIgnoringCase(Pageable pageable, String lastname); + Window findTop1ByLastnameOrderByFirstname(ScrollPosition scrollPosition, String lastname); + List findByLastnameIgnoringCaseLike(String lastname); List findByLastnameAndFirstnameAllIgnoringCase(String lastname, String firstname);