From 826147e7cbc1d51a76b539bd3e1adea04fcc6a22 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Sep 2024 09:44:40 +0200 Subject: [PATCH] Remove duplicated code. See: #2327 Original Pull Request: #3654 --- .../query/JpaKeysetScrollQueryCreator.java | 8 ++----- .../query/KeysetScrollDelegate.java | 22 +++++++++++++++++++ .../FetchableFluentQueryByPredicate.java | 8 +++---- .../support/SimpleJpaRepository.java | 10 ++++----- .../jpa/repository/UserRepositoryTests.java | 10 +++++++++ 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java index 25e7c25ca..9d767d004 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java @@ -21,8 +21,6 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.Sort; @@ -77,9 +75,7 @@ class JpaKeysetScrollQueryCreator extends JpaQueryCreator { Sort sortToUse = KeysetScrollSpecification.createSort(scrollPosition, sort, entityInformation); - Set selection = new LinkedHashSet<>(returnedType.getInputProperties()); - sortToUse.forEach(it -> selection.add(it.getProperty())); - - return selection; + return KeysetScrollDelegate.getProjectionInputProperties(entityInformation, returnedType.getInputProperties(), + sortToUse); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java index 2942fa0bc..66b9245b6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java @@ -16,7 +16,9 @@ package org.springframework.data.jpa.repository.query; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -24,6 +26,7 @@ import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.ScrollPosition.Direction; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.lang.Nullable; /** @@ -47,6 +50,25 @@ public class KeysetScrollDelegate { return direction == Direction.FORWARD ? FORWARD : REVERSE; } + /** + * Return a collection of property names required to construct a keyset selection query that include all keyset and + * identifier properties required to resume keyset scrolling. + * + * @param entity the underlying entity. + * @param projectionProperties projection property names. + * @param sort sort properties. + * @return a collection of property names required to construct a keyset selection query + */ + public static Collection getProjectionInputProperties(JpaEntityInformation entity, + Collection projectionProperties, Sort sort) { + + Collection properties = new LinkedHashSet<>(projectionProperties); + sort.forEach(it -> properties.add(it.getProperty())); + properties.addAll(entity.getIdAttributeNames()); + + return properties; + } + @Nullable public P createPredicate(KeysetScrollPosition keyset, Sort sort, QueryStrategy strategy) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java index 335b6e33b..2e9a1fbb2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java @@ -20,7 +20,6 @@ import jakarta.persistence.EntityManager; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; @@ -34,6 +33,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; +import org.springframework.data.jpa.repository.query.KeysetScrollDelegate; import org.springframework.data.jpa.repository.query.ScrollDelegate; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; @@ -219,13 +219,11 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp List inputProperties = returnedType.getInputProperties(); - if (returnedType.needsCustomConstruction() && !inputProperties.isEmpty()) { + if (returnedType.needsCustomConstruction()) { Collection requiredSelection; if (scrollPosition instanceof KeysetScrollPosition && returnedType.getReturnedType().isInterface()) { - requiredSelection = new LinkedHashSet<>(inputProperties); - sort.forEach(it -> requiredSelection.add(it.getProperty())); - entityInformation.getIdAttributeNames().forEach(requiredSelection::add); + requiredSelection = KeysetScrollDelegate.getProjectionInputProperties(entityInformation, inputProperties, sort); } else { requiredSelection = inputProperties; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index e3d1b561b..bb784e211 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -37,7 +37,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -58,6 +57,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.jpa.repository.query.KeysetScrollDelegate; import org.springframework.data.jpa.repository.query.KeysetScrollSpecification; import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.data.jpa.repository.support.FetchableFluentQueryBySpecification.SpecificationScrollDelegate; @@ -776,7 +776,7 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation inputProperties = returnedType.getInputProperties(); - if (returnedType.needsCustomConstruction() && !inputProperties.isEmpty()) { + if (returnedType.needsCustomConstruction()) { query = (CriteriaQuery) (returnedType.getReturnedType().isInterface() ? builder.createTupleQuery() : builder.createQuery(returnedType.getReturnedType())); } else { @@ -785,14 +785,12 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation root = applySpecificationToCriteria(spec, domainClass, query); - if (returnedType.needsCustomConstruction() && !inputProperties.isEmpty()) { + if (returnedType.needsCustomConstruction()) { Collection requiredSelection; if (scrollPosition instanceof KeysetScrollPosition && returnedType.getReturnedType().isInterface()) { - requiredSelection = new LinkedHashSet<>(inputProperties); - sort.stream().map(Sort.Order::getProperty).forEach(requiredSelection::add); - entityInformation.getIdAttributeNames().forEach(requiredSelection::add); + requiredSelection = KeysetScrollDelegate.getProjectionInputProperties(entityInformation, inputProperties, sort); } else { requiredSelection = inputProperties; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 766fb2199..4cc82549a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -2960,6 +2960,16 @@ class UserRepositoryTests { assertThat(users).hasSize(1); } + @Test // GH-2327 + void dynamicOpenProjectionReturningList() { + + flushTestUsers(); + + List users = repository.findAsListByFirstnameLike("%O%", UserProjectionUsingSpEL.class); + + assertThat(users).hasSize(1); + } + @Test // DATAJPA-1179 void duplicateSpelsWorkAsIntended() {