Polishing.

Migrate configuration setters for repository instances into JpaRepositoryConfigurationAware interface.

Revert ProjectionFactory propagation through CrudMethodMetadata.

Original pull request: #3432
See: #3410
This commit is contained in:
Mark Paluch
2024-04-19 11:19:27 +02:00
parent 0af9c62e04
commit 0086b38477
11 changed files with 125 additions and 91 deletions

View File

@@ -21,7 +21,6 @@ import java.lang.reflect.Method;
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();
}

View File

@@ -25,10 +25,10 @@ import java.util.Set;
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.EntityGraph;
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 @@ import org.springframework.util.ReflectionUtils;
class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware {
private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
private final Supplier<ProjectionFactory> projectionFactorySupplier;
CrudMethodMetadataPostProcessor(Supplier<ProjectionFactory> projectionFactorySupplier) {
this.projectionFactorySupplier = projectionFactorySupplier;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
@@ -76,8 +70,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
@Override
public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
factory
.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation, projectionFactorySupplier));
factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(repositoryInformation));
}
/**
@@ -109,14 +102,11 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
private final ConcurrentMap<Method, CrudMethodMetadata> metadataCache = new ConcurrentHashMap<>();
private final Set<Method> implementations = new HashSet<>();
private final Supplier<ProjectionFactory> projectionFactory;
CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation,
Supplier<ProjectionFactory> projectionFactory) {
CrudMethodMetadataPopulatingMethodInterceptor(RepositoryInformation repositoryInformation) {
ReflectionUtils.doWithMethods(repositoryInformation.getRepositoryInterface(), implementations::add,
method -> !repositoryInformation.isQueryMethod(method));
this.projectionFactory = projectionFactory;
}
/**
@@ -161,7 +151,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
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 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
private final @Nullable String comment;
private final Optional<EntityGraph> 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 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
return method;
}
@Override
public ProjectionFactory getProjectionFactory() {
return projectionFactory;
}
}
private static class ThreadBoundTargetSource implements TargetSource {

View File

@@ -96,8 +96,7 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
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 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
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 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
}
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 @@ class FetchableFluentQueryByPredicate<S, R> extends FluentQuerySupport<S, R> imp
return new FetchableFluentQueryByPredicate<>(predicate, entityType, resultType, sort, limit,
mergeProperties(properties), finder, scroll, pagedFinder, countOperation, existsOperation, entityManager,
getProjectionFactory());
projectionFactory);
}
@Override

View File

@@ -90,7 +90,7 @@ class FetchableFluentQueryBySpecification<S, R> extends FluentQuerySupport<S, R>
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 @@ class FetchableFluentQueryBySpecification<S, R> extends FluentQuerySupport<S, R>
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 @@ class FetchableFluentQueryBySpecification<S, R> extends FluentQuerySupport<S, R>
}
return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder,
scroll, countOperation, existsOperation, entityManager, getProjectionFactory());
scroll, countOperation, existsOperation, entityManager, projectionFactory);
}
@Override
public FetchableFluentQuery<R> project(Collection<String> properties) {
return new FetchableFluentQueryBySpecification<>(spec, entityType, resultType, sort, limit, properties, finder,
scroll, countOperation, existsOperation, entityManager, getProjectionFactory());
scroll, countOperation, existsOperation, entityManager, projectionFactory);
}
@Override

View File

@@ -27,7 +27,6 @@ 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;
/**
@@ -47,8 +46,7 @@ abstract class FluentQuerySupport<S, R> {
protected final int limit;
protected final Set<String> properties;
protected final Class<S> entityType;
private final ProjectionFactory projectionFactory;
protected final ProjectionFactory projectionFactory;
FluentQuerySupport(Class<R> resultType, Sort sort, int limit, @Nullable Collection<String> properties,
Class<S> entityType, ProjectionFactory projectionFactory) {
@@ -67,10 +65,6 @@ abstract class FluentQuerySupport<S, R> {
this.projectionFactory = projectionFactory;
}
ProjectionFactory getProjectionFactory() {
return projectionFactory;
}
final Collection<String> mergeProperties(Collection<String> additionalProperties) {
Set<String> newProperties = new HashSet<>();

View File

@@ -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.3
*/
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) {
}
}

View File

@@ -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 class JpaRepositoryFactory extends RepositoryFactorySupport {
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 class JpaRepositoryFactory extends RepositoryFactorySupport {
if (extractor.equals(PersistenceProvider.ECLIPSELINK)) {
addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager));
}
this.crudMethodMetadata = crudMethodMetadataPostProcessor.getCrudMethodMetadata();
}
@Override
@@ -192,8 +195,8 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
protected final JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information) {
JpaRepositoryImplementation<?, ?> repository = getTargetRepository(information, entityManager);
repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
repository.setEscapeCharacter(escapeCharacter);
invokeAwareMethods(repository);
return repository;
}
@@ -249,8 +252,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
@Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
return getRepositoryFragments(metadata, entityManager, entityPathResolver,
crudMethodMetadataPostProcessor.getCrudMethodMetadata());
return getRepositoryFragments(metadata, entityManager, entityPathResolver, this.crudMethodMetadata);
}
/**
@@ -279,13 +281,23 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
"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);

View File

@@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.support;
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 @@ import org.springframework.data.repository.NoRepositoryBean;
* @author Jens Schauder
*/
@NoRepositoryBean
public interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
public interface JpaRepositoryImplementation<T, ID>
extends JpaRepository<T, ID>, JpaSpecificationExecutor<T>, 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) {
}
}

View File

@@ -41,7 +41,6 @@ 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;
@@ -74,14 +73,15 @@ import com.querydsl.jpa.impl.AbstractJPAQuery;
* @author Greg Turnquist
* @author Yanming Zhou
*/
public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecutor<T> {
public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecutor<T>, JpaRepositoryConfigurationAware {
private final JpaEntityInformation<T, ?> entityInformation;
private final EntityPath<T> 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 class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
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<T> findOne(Predicate predicate) {
@@ -199,7 +209,7 @@ public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
select = (AbstractJPAQuery<?, ?>) 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 class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
this::count, //
this::exists, //
entityManager, //
getProjectionFactory()
);
getProjectionFactory());
return queryFunction.apply((FetchableFluentQuery<S>) fluentQuery);
}
@@ -336,12 +345,11 @@ public class QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateExecuto
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<Expression<?>, BooleanExpression> {

View File

@@ -105,6 +105,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
private final PersistenceProvider provider;
private @Nullable CrudMethodMetadata metadata;
private @Nullable ProjectionFactory projectionFactory;
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
/**
@@ -137,11 +138,11 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
* 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 class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
this.escapeCharacter = escapeCharacter;
}
@Override
public void setProjectionFactory(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
@Nullable
protected CrudMethodMetadata getRepositoryMethodMetadata() {
return metadata;
@@ -907,12 +913,11 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
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;
}
/**

View File

@@ -18,10 +18,10 @@ package org.springframework.data.jpa.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import jakarta.persistence.LockModeType;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test;
@@ -34,7 +34,6 @@ import org.mockito.quality.Strictness;
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;
@@ -57,7 +56,7 @@ class CrudMethodMetadataPopulatingMethodInterceptorUnitTests {
ProxyFactory factory = new ProxyFactory(new Object());
factory.addInterface(Sample.class);
factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(information, SpelAwareProxyProjectionFactory::new));
factory.addAdvice(new CrudMethodMetadataPopulatingMethodInterceptor(information));
factory.addAdvice(new MethodInterceptor() {
@Override
@@ -79,7 +78,7 @@ class CrudMethodMetadataPopulatingMethodInterceptorUnitTests {
when(information.getRepositoryInterface()).thenReturn((Class) Sample.class);
CrudMethodMetadataPopulatingMethodInterceptor interceptor = new CrudMethodMetadataPopulatingMethodInterceptor(
information, () -> new SpelAwareProxyProjectionFactory());
information);
interceptor.invoke(invocation);
assertThat(TransactionSynchronizationManager.getResource(method)).isNull();
@@ -89,7 +88,7 @@ class CrudMethodMetadataPopulatingMethodInterceptorUnitTests {
@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);