From 34c48623135d06cc4409fedc46099c04b7e69fff Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 17 Apr 2024 09:37:19 +0200 Subject: [PATCH 1/3] Prepare issue branch. --- pom.xml | 2 +- spring-data-envers/pom.xml | 4 ++-- spring-data-jpa-distribution/pom.xml | 2 +- spring-data-jpa/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index dd336ca646..b2b487ac99 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 470678d048..7bb04f9ed5 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 6bd074181c..9d2eed1835 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index 67ad4cabb2..f6138be568 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3410-SNAPSHOT ../pom.xml From 0d7e3628e6abf376ba23e8a2e107efb36c2a7c6b Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 17 Apr 2024 11:39:55 +0200 Subject: [PATCH 2/3] Fluent query API should use ProjectionFactory provided by the RepositoryFactory. This commit makes sure to push the ProjectionFactory down to the fluent query to make use of beans registered in the context. Prior to this change the fluent query variant would host its own factory not being aware of its surroundings. --- .../support/CrudMethodMetadata.java | 8 ++++++ .../CrudMethodMetadataPostProcessor.java | 26 ++++++++++++++++--- .../FetchableFluentQueryByPredicate.java | 23 +++++++++------- .../FetchableFluentQueryBySpecification.java | 25 ++++++++++-------- .../support/FluentQuerySupport.java | 11 ++++++-- .../support/JpaRepositoryFactory.java | 2 +- .../support/QuerydslJpaPredicateExecutor.java | 17 ++++++++++-- .../support/SimpleJpaRepository.java | 16 ++++++++++-- .../data/jpa/repository/GreetingsFrom.java | 26 +++++++++++++++++++ .../JavaConfigUserRepositoryTests.java | 5 ++++ .../jpa/repository/UserRepositoryTests.java | 26 +++++++++++++++++++ ...aPopulatingMethodInterceptorUnitTests.java | 7 ++--- ...chableFluentQueryByPredicateUnitTests.java | 2 +- .../test/resources/application-context.xml | 2 ++ .../config/namespace-application-context.xml | 2 ++ 15 files changed, 162 insertions(+), 36 deletions(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/GreetingsFrom.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index b6cff2cd28..918e05b42f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -21,6 +21,7 @@ import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.lang.Nullable; /** @@ -85,4 +86,11 @@ public interface CrudMethodMetadata { * @since 1.9 */ Method getMethod(); + + /** + * @return the {@link ProjectionFactory} to use or {@literal null} if not present. + * @since ?? + */ + @Nullable + ProjectionFactory getProjectionFactory(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 9daa2377be..215855cb11 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Predicate; +import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -38,6 +39,7 @@ import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Meta; import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; import org.springframework.lang.Nullable; @@ -61,6 +63,11 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware { private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + private final Supplier projectionFactorySupplier; + + CrudMethodMetadataPostProcessor(Supplier projectionFactorySupplier) { + this.projectionFactorySupplier = projectionFactorySupplier; + } @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -69,7 +76,8 @@ public void setBeanClassLoader(ClassLoader classLoader) { @Override public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { - factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation)); + factory + .addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation, projectionFactorySupplier)); } /** @@ -101,11 +109,14 @@ static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInte private final ConcurrentMap metadataCache = new ConcurrentHashMap<>(); private final Set implementations = new HashSet<>(); + private final Supplier projectionFactory; - CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) { + CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation, + Supplier projectionFactory) { ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add, method -> !repositoryInformation.isQueryMethod(method)); + this.projectionFactory = projectionFactory; } /** @@ -150,7 +161,7 @@ public Object invoke(MethodInvocation invocation) throws Throwable { if (methodMetadata == null) { - methodMetadata = new DefaultCrudMethodMetadata(method); + methodMetadata = new DefaultCrudMethodMetadata(method, projectionFactory.get()); CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata); if (tmp != null) { @@ -185,15 +196,17 @@ private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { private final @Nullable String comment; private final Optional entityGraph; private final Method method; + private ProjectionFactory projectionFactory; /** * Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. * * @param method must not be {@literal null}. */ - DefaultCrudMethodMetadata(Method method) { + DefaultCrudMethodMetadata(Method method, ProjectionFactory projectionFactory) { Assert.notNull(method, "Method must not be null"); + this.projectionFactory = projectionFactory; this.lockModeType = findLockModeType(method); this.queryHints = findQueryHints(method, it -> true); @@ -274,6 +287,11 @@ public Optional getEntityGraph() { public Method getMethod() { return method; } + + @Override + public ProjectionFactory getProjectionFactory() { + return projectionFactory; + } } private static class ThreadBoundTargetSource implements TargetSource { 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 b6e51c2904..91d9fc5bfa 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 @@ -34,6 +34,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; import org.springframework.data.jpa.repository.query.ScrollDelegate; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.util.Assert; @@ -51,6 +52,7 @@ * @author Mark Paluch * @author Jens Schauder * @author J.R. Onyschak + * @author Christoph Strobl * @since 2.6 */ class FetchableFluentQueryByPredicate extends FluentQuerySupport implements FetchableFluentQuery { @@ -64,21 +66,21 @@ class FetchableFluentQueryByPredicate extends FluentQuerySupport imp private final Function existsOperation; private final EntityManager entityManager; - public FetchableFluentQueryByPredicate(Predicate predicate, Class entityType, + FetchableFluentQueryByPredicate(Predicate predicate, Class entityType, Function> finder, PredicateScrollDelegate scroll, BiFunction> pagedFinder, Function countOperation, - Function existsOperation, EntityManager entityManager) { + Function existsOperation, EntityManager entityManager, ProjectionFactory projectionFactory) { this(predicate, entityType, (Class) entityType, Sort.unsorted(), 0, Collections.emptySet(), finder, scroll, - pagedFinder, countOperation, existsOperation, entityManager); + pagedFinder, countOperation, existsOperation, entityManager, projectionFactory); } private FetchableFluentQueryByPredicate(Predicate predicate, Class entityType, Class resultType, Sort sort, int limit, Collection properties, Function> finder, PredicateScrollDelegate scroll, BiFunction> pagedFinder, Function countOperation, Function existsOperation, - EntityManager entityManager) { + EntityManager entityManager, ProjectionFactory projectionFactory) { - super(resultType, sort, limit, properties, entityType); + super(resultType, sort, limit, properties, entityType, projectionFactory); this.predicate = predicate; this.finder = finder; this.scroll = scroll; @@ -94,7 +96,8 @@ public FetchableFluentQuery sortBy(Sort sort) { Assert.notNull(sort, "Sort must not be null"); return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, pagedFinder, countOperation, existsOperation, entityManager); + properties, finder, scroll, pagedFinder, countOperation, existsOperation, entityManager, + getProjectionFactory()); } @Override @@ -103,7 +106,7 @@ public FetchableFluentQuery limit(int limit) { Assert.isTrue(limit >= 0, "Limit must not be negative"); return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, properties, finder, - scroll, pagedFinder, countOperation, existsOperation, entityManager); + scroll, pagedFinder, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override @@ -116,14 +119,15 @@ public FetchableFluentQuery as(Class resultType) { } return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, properties, finder, - scroll, pagedFinder, countOperation, existsOperation, entityManager); + scroll, pagedFinder, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override public FetchableFluentQuery project(Collection properties) { return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, - mergeProperties(properties), finder, scroll, pagedFinder, countOperation, existsOperation, entityManager); + mergeProperties(properties), finder, scroll, pagedFinder, countOperation, existsOperation, entityManager, + getProjectionFactory()); } @Override @@ -230,7 +234,6 @@ private Function getConversionFunction() { return getConversionFunction(entityType, resultType); } - static class PredicateScrollDelegate extends ScrollDelegate { private final ScrollQueryFactory scrollFunction; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java index 57bec8597e..b42399052f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java @@ -36,6 +36,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.query.ScrollDelegate; import org.springframework.data.jpa.support.PageableUtils; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.util.Assert; @@ -47,6 +48,7 @@ * @param Domain type * @param Result type * @author Greg Turnquist + * @author Christoph Strobl * @since 3.0 */ class FetchableFluentQueryBySpecification extends FluentQuerySupport @@ -59,20 +61,21 @@ class FetchableFluentQueryBySpecification extends FluentQuerySupport private final Function, Boolean> existsOperation; private final EntityManager entityManager; - public FetchableFluentQueryBySpecification(Specification spec, Class entityType, - Function> finder, SpecificationScrollDelegate scrollDelegate, - Function, Long> countOperation, Function, Boolean> existsOperation, - EntityManager entityManager) { + FetchableFluentQueryBySpecification(Specification spec, Class entityType, Function> finder, + SpecificationScrollDelegate scrollDelegate, Function, Long> countOperation, + Function, Boolean> existsOperation, EntityManager entityManager, + ProjectionFactory projectionFactory) { this(spec, entityType, (Class) entityType, Sort.unsorted(), 0, Collections.emptySet(), finder, scrollDelegate, - countOperation, existsOperation, entityManager); + countOperation, existsOperation, entityManager, projectionFactory); } private FetchableFluentQueryBySpecification(Specification spec, Class entityType, Class resultType, Sort sort, int limit, Collection properties, Function> finder, SpecificationScrollDelegate scrollDelegate, Function, Long> countOperation, - Function, Boolean> existsOperation, EntityManager entityManager) { + Function, Boolean> existsOperation, EntityManager entityManager, + ProjectionFactory projectionFactory) { - super(resultType, sort, limit, properties, entityType); + super(resultType, sort, limit, properties, entityType, projectionFactory); this.spec = spec; this.finder = finder; this.scroll = scrollDelegate; @@ -87,7 +90,7 @@ public FetchableFluentQuery sortBy(Sort sort) { Assert.notNull(sort, "Sort must not be null"); return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager); + properties, finder, scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override @@ -96,7 +99,7 @@ public FetchableFluentQuery limit(int limit) { Assert.isTrue(limit >= 0, "Limit must not be negative"); return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager); + properties, finder, scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override @@ -108,14 +111,14 @@ public FetchableFluentQuery as(Class resultType) { } return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, - scroll, countOperation, existsOperation, entityManager); + scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override public FetchableFluentQuery project(Collection properties) { return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, - scroll, countOperation, existsOperation, entityManager); + scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java index 4cc28c3df2..d37cfd6042 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java @@ -26,6 +26,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.lang.Nullable; @@ -36,6 +37,7 @@ * @author Greg Turnquist * @author Jens Schauder * @author Mark Paluch + * @author Christoph Strobl * @since 2.6 */ abstract class FluentQuerySupport { @@ -46,10 +48,10 @@ abstract class FluentQuerySupport { protected final Set properties; protected final Class entityType; - private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); + private final ProjectionFactory projectionFactory; FluentQuerySupport(Class resultType, Sort sort, int limit, @Nullable Collection properties, - Class entityType) { + Class entityType, ProjectionFactory projectionFactory) { this.resultType = resultType; this.sort = sort; @@ -62,6 +64,11 @@ abstract class FluentQuerySupport { } this.entityType = entityType; + this.projectionFactory = projectionFactory; + } + + ProjectionFactory getProjectionFactory() { + return projectionFactory; } final Collection mergeProperties(Collection additionalProperties) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index a0ec78a0c7..0c0145026e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -100,7 +100,7 @@ public JpaRepositoryFactory(EntityManager entityManager) { this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); - this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); + this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(() -> getProjectionFactory()); this.entityPathResolver = SimpleEntityPathResolver.INSTANCE; this.queryMethodFactory = new DefaultJpaQueryMethodFactory(extractor); this.queryRewriterProvider = QueryRewriterProvider.simple(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java index 606d56631e..3b3be3b721 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -36,9 +36,12 @@ import org.springframework.data.jpa.repository.query.KeysetScrollSpecification; import org.springframework.data.jpa.repository.support.FetchableFluentQueryByPredicate.PredicateScrollDelegate; import org.springframework.data.jpa.repository.support.FluentQuerySupport.ScrollQueryFactory; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.lang.Nullable; @@ -223,7 +226,8 @@ public R findBy(Predicate predicate, Function) fluentQuery); @@ -251,7 +255,6 @@ public boolean exists(Predicate predicate) { AbstractJPAQuery query = doCreateQuery(getQueryHints().withFetchGraphs(entityManager), predicate); CrudMethodMetadata metadata = getRepositoryMethodMetadata(); - if (metadata == null) { return query; } @@ -331,6 +334,16 @@ private List executeSorted(JPQLQuery query, Sort sort) { return querydsl.applySorting(sort, query).fetch(); } + private ProjectionFactory getProjectionFactory() { + + CrudMethodMetadata metadata = getRepositoryMethodMetadata(); + if(metadata == null || metadata.getProjectionFactory() == null) { + return new SpelAwareProxyProjectionFactory(); + } + + return metadata.getProjectionFactory(); + } + class QuerydslQueryStrategy implements QueryStrategy, BooleanExpression> { @Override 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 879e683865..c16727c9a0 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 @@ -61,6 +61,8 @@ import org.springframework.data.jpa.repository.support.FluentQuerySupport.ScrollQueryFactory; import org.springframework.data.jpa.repository.support.QueryHints.NoHints; import org.springframework.data.jpa.support.PageableUtils; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.util.ProxyUtils; @@ -524,8 +526,8 @@ private R doFindBy(Specification spec, Class domainClass, SpecificationScrollDelegate scrollDelegate = new SpecificationScrollDelegate<>(scrollFunction, entityInformation); - FetchableFluentQuery fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass, finder, - scrollDelegate, this::count, this::exists, this.entityManager); + FetchableFluentQueryBySpecification fluentQuery = new FetchableFluentQueryBySpecification<>(spec, domainClass, finder, + scrollDelegate, this::count, this::exists, this.entityManager, getProjectionFactory()); return queryFunction.apply((FetchableFluentQuery) fluentQuery); } @@ -903,6 +905,16 @@ private void applyComment(CrudMethodMetadata metadata, BiConsumer users = repository.findBy( + of(prototype, + matching().withIgnorePaths("age", "createdAt", "active").withMatcher("firstname", + GenericPropertyMatcher::contains)), // + q -> q.as(UserProjectionUsingSpEL.class).all()); + + assertThat(users).extracting(UserProjectionUsingSpEL::hello) + .contains(new GreetingsFrom().groot(firstUser.getFirstname())); + } + @Test // GH-2294 void findByFluentExampleWithSimplePropertyPathsDoesntLoadUnrequestedPaths() { @@ -3364,4 +3384,10 @@ private Page executeSpecWithSort(Sort sort) { private interface UserProjectionInterfaceBased { String getFirstname(); } + + private interface UserProjectionUsingSpEL { + + @Value("#{@greetingsFrom.groot(target.firstname)}") + String hello(); + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java index 143a3c15da..9824407c95 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java @@ -34,6 +34,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -56,7 +57,7 @@ private static Sample expectLockModeType(CrudMethodMetadata metadata, Repository ProxyFactory factory = new ProxyFactory(new Object()); factory.addInterface(Sample.class); - factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(information)); + factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(information, SpelAwareProxyProjectionFactory::new)); factory.addAdvice(new MethodInterceptor() { @Override @@ -78,7 +79,7 @@ void cleansUpBoundResources() throws Throwable { when(information.getRepositoryInterface()).thenReturn((Class) Sample.class); CrudMethodMetadataPopulatingMethodInterceptor interceptor = new CrudMethodMetadataPopulatingMethodInterceptor( - information); + information, () -> new SpelAwareProxyProjectionFactory()); interceptor.invoke(invocation); assertThat(TransactionSynchronizationManager.getResource(method)).isNull(); @@ -88,7 +89,7 @@ void cleansUpBoundResources() throws Throwable { @SuppressWarnings("unchecked") void looksUpCrudMethodMetadataForEveryInvocation() { - CrudMethodMetadata metadata = new CrudMethodMetadataPostProcessor().getCrudMethodMetadata(); + CrudMethodMetadata metadata = new CrudMethodMetadataPostProcessor(() -> new SpelAwareProxyProjectionFactory()).getCrudMethodMetadata(); when(information.isQueryMethod(any())).thenReturn(false); when(information.getRepositoryInterface()).thenReturn((Class) Sample.class); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java index 767ac14cbe..f2c5e9f00c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicateUnitTests.java @@ -35,7 +35,7 @@ void multipleSortBy() { Sort s1 = Sort.by(Order.by("s1")); Sort s2 = Sort.by(Order.by("s2")); FetchableFluentQueryByPredicate f = new FetchableFluentQueryByPredicate(null, null, null, null, null, null, null, - null); + null, null); f = (FetchableFluentQueryByPredicate) f.sortBy(s1).sortBy(s2); assertThat(f.sort).isEqualTo(s1.and(s2)); } diff --git a/spring-data-jpa/src/test/resources/application-context.xml b/spring-data-jpa/src/test/resources/application-context.xml index 74529a5212..1bd58b22cd 100644 --- a/spring-data-jpa/src/test/resources/application-context.xml +++ b/spring-data-jpa/src/test/resources/application-context.xml @@ -43,4 +43,6 @@ + + diff --git a/spring-data-jpa/src/test/resources/config/namespace-application-context.xml b/spring-data-jpa/src/test/resources/config/namespace-application-context.xml index e192050ec6..b5c02d868d 100644 --- a/spring-data-jpa/src/test/resources/config/namespace-application-context.xml +++ b/spring-data-jpa/src/test/resources/config/namespace-application-context.xml @@ -28,4 +28,6 @@ + + From 3aa468fe73af48b80b5c4b7c72332a62d242591c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 19 Apr 2024 11:19:27 +0200 Subject: [PATCH 3/3] Polishing. Migrate configuration setters for repository instances into JpaRepositoryConfigurationAware interface. Revert ProjectionFactory propagation through CrudMethodMetadata. --- .../support/CrudMethodMetadata.java | 7 --- .../CrudMethodMetadataPostProcessor.java | 26 ++------- .../FetchableFluentQueryByPredicate.java | 9 ++- .../FetchableFluentQueryBySpecification.java | 8 +-- .../support/FluentQuerySupport.java | 8 +-- .../JpaRepositoryConfigurationAware.java | 55 +++++++++++++++++++ .../support/JpaRepositoryFactory.java | 26 +++++++-- .../support/JpaRepositoryImplementation.java | 19 +------ .../support/QuerydslJpaPredicateExecutor.java | 28 ++++++---- .../support/SimpleJpaRepository.java | 19 ++++--- ...aPopulatingMethodInterceptorUnitTests.java | 11 ++-- 11 files changed, 126 insertions(+), 90 deletions(-) create mode 100644 spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index 918e05b42f..ae738974b1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -21,7 +21,6 @@ import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.projection.ProjectionFactory; import org.springframework.lang.Nullable; /** @@ -87,10 +86,4 @@ public interface CrudMethodMetadata { */ Method getMethod(); - /** - * @return the {@link ProjectionFactory} to use or {@literal null} if not present. - * @since ?? - */ - @Nullable - ProjectionFactory getProjectionFactory(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 215855cb11..246e82dcd6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -25,10 +25,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Predicate; -import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.aop.TargetSource; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; @@ -39,7 +39,6 @@ import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Meta; import org.springframework.data.jpa.repository.QueryHints; -import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; import org.springframework.lang.Nullable; @@ -63,11 +62,6 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware { private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); - private final Supplier projectionFactorySupplier; - - CrudMethodMetadataPostProcessor(Supplier projectionFactorySupplier) { - this.projectionFactorySupplier = projectionFactorySupplier; - } @Override public void setBeanClassLoader(ClassLoader classLoader) { @@ -76,8 +70,7 @@ public void setBeanClassLoader(ClassLoader classLoader) { @Override public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { - factory - .addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation, projectionFactorySupplier)); + factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation)); } /** @@ -109,14 +102,11 @@ static class CrudMethodMetadataPopulatingMethodInterceptor implements MethodInte private final ConcurrentMap metadataCache = new ConcurrentHashMap<>(); private final Set implementations = new HashSet<>(); - private final Supplier projectionFactory; - CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation, - Supplier projectionFactory) { + CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) { ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add, method -> !repositoryInformation.isQueryMethod(method)); - this.projectionFactory = projectionFactory; } /** @@ -161,7 +151,7 @@ public Object invoke(MethodInvocation invocation) throws Throwable { if (methodMetadata == null) { - methodMetadata = new DefaultCrudMethodMetadata(method, projectionFactory.get()); + methodMetadata = new DefaultCrudMethodMetadata(method); CrudMethodMetadata tmp = metadataCache.putIfAbsent(method, methodMetadata); if (tmp != null) { @@ -196,17 +186,15 @@ private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { private final @Nullable String comment; private final Optional entityGraph; private final Method method; - private ProjectionFactory projectionFactory; /** * Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. * * @param method must not be {@literal null}. */ - DefaultCrudMethodMetadata(Method method, ProjectionFactory projectionFactory) { + DefaultCrudMethodMetadata(Method method) { Assert.notNull(method, "Method must not be null"); - this.projectionFactory = projectionFactory; this.lockModeType = findLockModeType(method); this.queryHints = findQueryHints(method, it -> true); @@ -288,10 +276,6 @@ public Method getMethod() { return method; } - @Override - public ProjectionFactory getProjectionFactory() { - return projectionFactory; - } } private static class ThreadBoundTargetSource implements TargetSource { 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 91d9fc5bfa..9ed0a0ce3e 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 @@ -96,8 +96,7 @@ public FetchableFluentQuery sortBy(Sort sort) { Assert.notNull(sort, "Sort must not be null"); return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, pagedFinder, countOperation, existsOperation, entityManager, - getProjectionFactory()); + properties, finder, scroll, pagedFinder, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -106,7 +105,7 @@ public FetchableFluentQuery limit(int limit) { Assert.isTrue(limit >= 0, "Limit must not be negative"); return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, properties, finder, - scroll, pagedFinder, countOperation, existsOperation, entityManager, getProjectionFactory()); + scroll, pagedFinder, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -119,7 +118,7 @@ public FetchableFluentQuery as(Class resultType) { } return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, properties, finder, - scroll, pagedFinder, countOperation, existsOperation, entityManager, getProjectionFactory()); + scroll, pagedFinder, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -127,7 +126,7 @@ public FetchableFluentQuery project(Collection properties) { return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit, mergeProperties(properties), finder, scroll, pagedFinder, countOperation, existsOperation, entityManager, - getProjectionFactory()); + projectionFactory); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java index b42399052f..6121be4374 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java @@ -90,7 +90,7 @@ public FetchableFluentQuery sortBy(Sort sort) { Assert.notNull(sort, "Sort must not be null"); return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); + properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -99,7 +99,7 @@ public FetchableFluentQuery limit(int limit) { Assert.isTrue(limit >= 0, "Limit must not be negative"); return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, this.sort.and(sort), limit, - properties, finder, scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); + properties, finder, scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override @@ -111,14 +111,14 @@ public FetchableFluentQuery as(Class resultType) { } return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, - scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override public FetchableFluentQuery project(Collection properties) { return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder, - scroll, countOperation, existsOperation, entityManager, getProjectionFactory()); + scroll, countOperation, existsOperation, entityManager, projectionFactory); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java index d37cfd6042..5917a119f5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java @@ -27,7 +27,6 @@ import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.lang.Nullable; /** @@ -47,8 +46,7 @@ abstract class FluentQuerySupport { protected final int limit; protected final Set properties; protected final Class entityType; - - private final ProjectionFactory projectionFactory; + protected final ProjectionFactory projectionFactory; FluentQuerySupport(Class resultType, Sort sort, int limit, @Nullable Collection properties, Class entityType, ProjectionFactory projectionFactory) { @@ -67,10 +65,6 @@ abstract class FluentQuerySupport { this.projectionFactory = projectionFactory; } - ProjectionFactory getProjectionFactory() { - return projectionFactory; - } - final Collection mergeProperties(Collection additionalProperties) { Set newProperties = new HashSet<>(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java new file mode 100644 index 0000000000..942e209a10 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryConfigurationAware.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.support; + +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.projection.ProjectionFactory; + +/** + * Interface to be implemented by classes that want to be aware of their configuration in a JPA repository context. + * + * @author Mark Paluch + * @since 3.2.6 + */ +public interface JpaRepositoryConfigurationAware { + + /** + * Configures the {@link EscapeCharacter} to be used with the repository. + * + * @param escapeCharacter must not be {@literal null}. + */ + default void setEscapeCharacter(EscapeCharacter escapeCharacter) { + + } + + /** + * Configures the {@link ProjectionFactory} to be used with the repository. + * + * @param projectionFactory must not be {@literal null}. + */ + default void setProjectionFactory(ProjectionFactory projectionFactory) { + + } + + /** + * Configures the {@link CrudMethodMetadata} to be used with the repository. + * + * @param metadata must not be {@literal null}. + */ + default void setRepositoryMethodMetadata(CrudMethodMetadata metadata) { + + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 0c0145026e..69ef36ee65 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -83,6 +83,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { private final EntityManager entityManager; private final QueryExtractor extractor; private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; + private final CrudMethodMetadata crudMethodMetadata; private EntityPathResolver entityPathResolver; private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT; @@ -100,7 +101,7 @@ public JpaRepositoryFactory(EntityManager entityManager) { this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); - this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(() -> getProjectionFactory()); + this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); this.entityPathResolver = SimpleEntityPathResolver.INSTANCE; this.queryMethodFactory = new DefaultJpaQueryMethodFactory(extractor); this.queryRewriterProvider = QueryRewriterProvider.simple(); @@ -116,6 +117,8 @@ public JpaRepositoryFactory(EntityManager entityManager) { if (extractor.equals(PersistenceProvider.ECLIPSELINK)) { addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager)); } + + this.crudMethodMetadata = crudMethodMetadataPostProcessor.getCrudMethodMetadata(); } @Override @@ -192,12 +195,13 @@ public void setQueryRewriterProvider(QueryRewriterProvider queryRewriterProvider protected final JpaRepositoryImplementation getTargetRepository(RepositoryInformation information) { JpaRepositoryImplementation repository = getTargetRepository(information, entityManager); - repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata()); - repository.setEscapeCharacter(escapeCharacter); + + invokeAwareMethods(repository); return repository; } + /** * Callback to create a {@link JpaRepository} instance with the given {@link EntityManager} * @@ -250,7 +254,7 @@ public JpaEntityInformation getEntityInformation(Class domainC protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { return getRepositoryFragments(metadata, entityManager, entityPathResolver, - crudMethodMetadataPostProcessor.getCrudMethodMetadata()); + this.crudMethodMetadata); } /** @@ -279,13 +283,23 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata "Cannot combine Querydsl and reactive repository support in a single interface"); } - return RepositoryFragments.just(new QuerydslJpaPredicateExecutor<>(getEntityInformation(metadata.getDomainType()), - entityManager, resolver, crudMethodMetadata)); + QuerydslJpaPredicateExecutor querydslJpaPredicateExecutor = new QuerydslJpaPredicateExecutor<>( + getEntityInformation(metadata.getDomainType()), entityManager, resolver, crudMethodMetadata); + invokeAwareMethods(querydslJpaPredicateExecutor); + + return RepositoryFragments.just(querydslJpaPredicateExecutor); } return RepositoryFragments.empty(); } + private void invokeAwareMethods(JpaRepositoryConfigurationAware repository) { + + repository.setRepositoryMethodMetadata(crudMethodMetadata); + repository.setEscapeCharacter(escapeCharacter); + repository.setProjectionFactory(getProjectionFactory()); + } + private static boolean isTransactionNeeded(Class repositoryClass) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(repositoryClass); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java index 9984675ac4..d3a5b1c5fe 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java @@ -17,7 +17,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.repository.NoRepositoryBean; /** @@ -28,21 +27,7 @@ * @author Jens Schauder */ @NoRepositoryBean -public interface JpaRepositoryImplementation extends JpaRepository, JpaSpecificationExecutor { +public interface JpaRepositoryImplementation + extends JpaRepository, JpaSpecificationExecutor, JpaRepositoryConfigurationAware { - /** - * Configures the {@link CrudMethodMetadata} to be used with the repository. - * - * @param crudMethodMetadata must not be {@literal null}. - */ - void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata); - - /** - * Configures the {@link EscapeCharacter} to be used with the repository. - * - * @param escapeCharacter Must not be {@literal null}. - */ - default void setEscapeCharacter(EscapeCharacter escapeCharacter) { - - } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java index 3b3be3b721..c16f95c0a1 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -41,7 +41,6 @@ import org.springframework.data.querydsl.EntityPathResolver; import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; -import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.lang.Nullable; @@ -74,14 +73,15 @@ * @author Greg Turnquist * @author Yanming Zhou */ -public class QuerydslJpaPredicateExecutor implements QuerydslPredicateExecutor { +public class QuerydslJpaPredicateExecutor implements QuerydslPredicateExecutor, JpaRepositoryConfigurationAware { private final JpaEntityInformation entityInformation; private final EntityPath path; private final Querydsl querydsl; private final QuerydslQueryStrategy scrollQueryAdapter; private final EntityManager entityManager; - private final CrudMethodMetadata metadata; + private @Nullable CrudMethodMetadata metadata; + private @Nullable ProjectionFactory projectionFactory; /** * Creates a new {@link QuerydslJpaPredicateExecutor} from the given domain class and {@link EntityManager} and uses @@ -103,6 +103,16 @@ public QuerydslJpaPredicateExecutor(JpaEntityInformation entityInformation this.scrollQueryAdapter = new QuerydslQueryStrategy(); } + @Override + public void setRepositoryMethodMetadata(CrudMethodMetadata metadata) { + this.metadata = metadata; + } + + @Override + public void setProjectionFactory(ProjectionFactory projectionFactory) { + this.projectionFactory = projectionFactory; + } + @Override public Optional findOne(Predicate predicate) { @@ -199,7 +209,7 @@ public R findBy(Predicate predicate, Function) querydsl.applySorting(sort, select); if (scrollPosition instanceof OffsetScrollPosition offset) { - if(!offset.isInitial()) { + if (!offset.isInitial()) { select.offset(offset.getOffset() + 1); } } @@ -227,8 +237,7 @@ public R findBy(Predicate predicate, Function) fluentQuery); } @@ -336,12 +345,11 @@ private List executeSorted(JPQLQuery query, Sort sort) { private ProjectionFactory getProjectionFactory() { - CrudMethodMetadata metadata = getRepositoryMethodMetadata(); - if(metadata == null || metadata.getProjectionFactory() == null) { - return new SpelAwareProxyProjectionFactory(); + if (projectionFactory == null) { + projectionFactory = new SpelAwareProxyProjectionFactory(); } - return metadata.getProjectionFactory(); + return projectionFactory; } class QuerydslQueryStrategy implements QueryStrategy, BooleanExpression> { 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 c16727c9a0..50afeee8a5 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 @@ -105,6 +105,7 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation domainClass, EntityManager entityManager) { * Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be * applied to queries. * - * @param crudMethodMetadata + * @param metadata */ @Override - public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) { - this.metadata = crudMethodMetadata; + public void setRepositoryMethodMetadata(CrudMethodMetadata metadata) { + this.metadata = metadata; } @Override @@ -149,6 +150,11 @@ public void setEscapeCharacter(EscapeCharacter escapeCharacter) { this.escapeCharacter = escapeCharacter; } + @Override + public void setProjectionFactory(ProjectionFactory projectionFactory) { + this.projectionFactory = projectionFactory; + } + @Nullable protected CrudMethodMetadata getRepositoryMethodMetadata() { return metadata; @@ -907,12 +913,11 @@ private void applyComment(CrudMethodMetadata metadata, BiConsumer new SpelAwareProxyProjectionFactory()); + information); interceptor.invoke(invocation); assertThat(TransactionSynchronizationManager.getResource(method)).isNull(); @@ -89,7 +88,7 @@ void cleansUpBoundResources() throws Throwable { @SuppressWarnings("unchecked") void looksUpCrudMethodMetadataForEveryInvocation() { - CrudMethodMetadata metadata = new CrudMethodMetadataPostProcessor(() -> new SpelAwareProxyProjectionFactory()).getCrudMethodMetadata(); + CrudMethodMetadata metadata = new CrudMethodMetadataPostProcessor().getCrudMethodMetadata(); when(information.isQueryMethod(any())).thenReturn(false); when(information.getRepositoryInterface()).thenReturn((Class) Sample.class);