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
This commit is contained in:
@@ -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<T, ID> 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<T, ID> extends JpaEntityInformationSu
|
||||
@Override
|
||||
public Map<String, Object> getKeyset(Iterable<String> 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<String, Object> getter = getPropertyValueFunction(entity);
|
||||
|
||||
Map<String, Object> 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<String, Object> 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.
|
||||
*
|
||||
|
||||
@@ -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<NameOnly> first = userRepository.findTop1ByLastnameOrderByFirstname(ScrollPosition.keyset(), //
|
||||
"Matthews");
|
||||
|
||||
Window<NameOnly> 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() {
|
||||
|
||||
|
||||
@@ -226,6 +226,8 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
|
||||
|
||||
Page<User> findByLastnameIgnoringCase(Pageable pageable, String lastname);
|
||||
|
||||
Window<NameOnly> findTop1ByLastnameOrderByFirstname(ScrollPosition scrollPosition, String lastname);
|
||||
|
||||
List<User> findByLastnameIgnoringCaseLike(String lastname);
|
||||
|
||||
List<User> findByLastnameAndFirstnameAllIgnoringCase(String lastname, String firstname);
|
||||
|
||||
Reference in New Issue
Block a user