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:
Mark Paluch
2023-07-20 11:57:48 +02:00
parent d6bcdf36e1
commit 5f84e2aa63
3 changed files with 41 additions and 5 deletions

View File

@@ -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.
*

View File

@@ -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() {

View File

@@ -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);