Skip to content

Commit 040508a

Browse files
committed
Provide JpaRepositoryFragmentsContributor in JPA Repository Factory and Repository Factory Bean.
Closes #3874
1 parent 15bd025 commit 040508a

13 files changed

+454
-171
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java

Lines changed: 5 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,10 @@
4343
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4444
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
4545
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
46-
import org.springframework.beans.factory.support.RegisteredBean;
4746
import org.springframework.beans.factory.support.RootBeanDefinition;
4847
import org.springframework.context.annotation.AnnotationConfigUtils;
4948
import org.springframework.core.annotation.AnnotationAttributes;
5049
import org.springframework.core.io.ResourceLoader;
51-
import org.springframework.core.type.classreading.MetadataReaderFactory;
52-
import org.springframework.core.type.filter.TypeFilter;
5350
import org.springframework.dao.DataAccessException;
5451
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
5552
import org.springframework.data.aot.AotContext;
@@ -63,14 +60,10 @@
6360
import org.springframework.data.repository.aot.generate.RepositoryContributor;
6461
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
6562
import org.springframework.data.repository.config.AotRepositoryContext;
66-
import org.springframework.data.repository.config.ImplementationDetectionConfiguration;
67-
import org.springframework.data.repository.config.ImplementationLookupConfiguration;
68-
import org.springframework.data.repository.config.RepositoryConfiguration;
6963
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
7064
import org.springframework.data.repository.config.RepositoryConfigurationSource;
7165
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
7266
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
73-
import org.springframework.data.util.Streamable;
7467
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
7568
import org.springframework.util.ClassUtils;
7669
import org.springframework.util.StringUtils;
@@ -105,6 +98,11 @@ public String getModuleName() {
10598
return "JPA";
10699
}
107100

101+
@Override
102+
public String getRepositoryBaseClassName() {
103+
return SimpleJpaRepository.class.getName();
104+
}
105+
108106
@Override
109107
public String getRepositoryFactoryBeanClassName() {
110108
return JpaRepositoryFactoryBean.class.getName();
@@ -342,123 +340,5 @@ public static class JpaRepositoryRegistrationAotProcessor extends RepositoryRegi
342340
return emf != null ? new JpaRepositoryContributor(repositoryContext, emf)
343341
: new JpaRepositoryContributor(repositoryContext);
344342
}
345-
346-
@Nullable
347-
@Override
348-
@SuppressWarnings("NullAway")
349-
protected RepositoryConfiguration<?> getRepositoryMetadata(RegisteredBean bean) {
350-
RepositoryConfiguration<?> configuration = super.getRepositoryMetadata(bean);
351-
352-
if (configuration != null && configuration.getRepositoryBaseClassName().isPresent()) {
353-
return configuration;
354-
}
355-
return new Meh<>(configuration);
356-
}
357-
}
358-
359-
/**
360-
* I'm just a dirty hack so we can refine the {@link #getRepositoryBaseClassName()} method as we cannot instantiate
361-
* the bean safely to extract it form the repository factory in data commons. So we either have a configurable
362-
* {@link RepositoryConfiguration} return from
363-
* {@link RepositoryRegistrationAotProcessor#getRepositoryMetadata(RegisteredBean)} or change the arrangement and
364-
* maybe move the type out of the factoy.
365-
*
366-
* @param <T>
367-
*/
368-
static class Meh<T extends RepositoryConfigurationSource> implements RepositoryConfiguration<T> {
369-
370-
private RepositoryConfiguration<?> configuration;
371-
372-
public Meh(RepositoryConfiguration<?> configuration) {
373-
this.configuration = configuration;
374-
}
375-
376-
@Nullable
377-
@Override
378-
public Object getSource() {
379-
return configuration.getSource();
380-
}
381-
382-
@Override
383-
public T getConfigurationSource() {
384-
return (T) configuration.getConfigurationSource();
385-
}
386-
387-
@Override
388-
public boolean isLazyInit() {
389-
return configuration.isLazyInit();
390-
}
391-
392-
@Override
393-
public boolean isPrimary() {
394-
return configuration.isPrimary();
395-
}
396-
397-
@Override
398-
public Streamable<String> getBasePackages() {
399-
return configuration.getBasePackages();
400-
}
401-
402-
@Override
403-
public Streamable<String> getImplementationBasePackages() {
404-
return configuration.getImplementationBasePackages();
405-
}
406-
407-
@Override
408-
public String getRepositoryInterface() {
409-
return configuration.getRepositoryInterface();
410-
}
411-
412-
@Override
413-
public Optional<Object> getQueryLookupStrategyKey() {
414-
return Optional.ofNullable(configuration.getQueryLookupStrategyKey());
415-
}
416-
417-
@Override
418-
public Optional<String> getNamedQueriesLocation() {
419-
return configuration.getNamedQueriesLocation();
420-
}
421-
422-
@Override
423-
public Optional<String> getRepositoryBaseClassName() {
424-
String name = SimpleJpaRepository.class.getName();
425-
return Optional.of(name);
426-
}
427-
428-
@Override
429-
public String getRepositoryFactoryBeanClassName() {
430-
return configuration.getRepositoryFactoryBeanClassName();
431-
}
432-
433-
@Override
434-
public String getImplementationBeanName() {
435-
return configuration.getImplementationBeanName();
436-
}
437-
438-
@Override
439-
public String getRepositoryBeanName() {
440-
return configuration.getRepositoryBeanName();
441-
}
442-
443-
@Override
444-
public Streamable<TypeFilter> getExcludeFilters() {
445-
return configuration.getExcludeFilters();
446-
}
447-
448-
@Override
449-
public ImplementationDetectionConfiguration toImplementationDetectionConfiguration(MetadataReaderFactory factory) {
450-
return configuration.toImplementationDetectionConfiguration(factory);
451-
}
452-
453-
@Override
454-
public ImplementationLookupConfiguration toLookupConfiguration(MetadataReaderFactory factory) {
455-
return configuration.toLookupConfiguration(factory);
456-
}
457-
458-
@Nullable
459-
@Override
460-
public String getResourceDescription() {
461-
return configuration.getResourceDescription();
462-
}
463343
}
464344
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformationSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
public abstract class JpaEntityInformationSupport<T, ID> extends AbstractEntityInformation<T, ID>
3636
implements JpaEntityInformation<T, ID> {
3737

38-
private JpaEntityMetadata<T> metadata;
38+
private final JpaEntityMetadata<T> metadata;
3939

4040
/**
4141
* Creates a new {@link JpaEntityInformationSupport} with the given domain class.

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package org.springframework.data.jpa.repository.support;
1717

18-
import static org.springframework.data.querydsl.QuerydslUtils.*;
19-
2018
import jakarta.persistence.EntityManager;
2119
import jakarta.persistence.Tuple;
2220

@@ -32,15 +30,13 @@
3230
import org.springframework.beans.BeanUtils;
3331
import org.springframework.beans.BeansException;
3432
import org.springframework.beans.factory.BeanFactory;
35-
import org.springframework.dao.InvalidDataAccessApiUsageException;
3633
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
3734
import org.springframework.data.jpa.provider.PersistenceProvider;
3835
import org.springframework.data.jpa.repository.JpaRepository;
3936
import org.springframework.data.jpa.repository.query.*;
4037
import org.springframework.data.jpa.util.JpaMetamodel;
4138
import org.springframework.data.projection.ProjectionFactory;
4239
import org.springframework.data.querydsl.EntityPathResolver;
43-
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
4440
import org.springframework.data.querydsl.SimpleEntityPathResolver;
4541
import org.springframework.data.repository.core.RepositoryInformation;
4642
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -79,6 +75,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
7975

8076
private EntityPathResolver entityPathResolver;
8177
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
78+
private JpaRepositoryFragmentsContributor fragmentsContributor = JpaRepositoryFragmentsContributor.DEFAULT;
8279
private QueryEnhancerSelector queryEnhancerSelector = QueryEnhancerSelector.DEFAULT_SELECTOR;
8380
private JpaQueryMethodFactory queryMethodFactory;
8481
private QueryRewriterProvider queryRewriterProvider;
@@ -159,6 +156,17 @@ public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
159156
this.escapeCharacter = escapeCharacter;
160157
}
161158

159+
/**
160+
* Configures the {@link JpaRepositoryFragmentsContributor} to be used. Defaults to
161+
* {@link JpaRepositoryFragmentsContributor#DEFAULT}.
162+
*
163+
* @param fragmentsContributor
164+
* @since 4.0
165+
*/
166+
public void setFragmentsContributor(JpaRepositoryFragmentsContributor fragmentsContributor) {
167+
this.fragmentsContributor = fragmentsContributor;
168+
}
169+
162170
/**
163171
* Configures the {@link JpaQueryMethodFactory} to be used. Defaults to {@link DefaultJpaQueryMethodFactory}.
164172
*
@@ -259,51 +267,39 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key
259267
@Override
260268
@SuppressWarnings("unchecked")
261269
public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
262-
263270
return (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
264271
}
265272

266273
@Override
267274
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
268-
269275
return getRepositoryFragments(metadata, entityManager, entityPathResolver, this.crudMethodMetadata);
270276
}
271277

272278
/**
273-
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add JPA-specific extensions. Typically
279+
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add JPA-specific extensions. Typically,
274280
* adds a {@link QuerydslJpaPredicateExecutor} if the repository interface uses Querydsl.
275281
* <p>
276-
* Can be overridden by subclasses to customize {@link RepositoryFragments}.
282+
* Built-in fragment contribution can be customized by configuring {@link JpaRepositoryFragmentsContributor}.
277283
*
278284
* @param metadata repository metadata.
279285
* @param entityManager the entity manager.
280286
* @param resolver resolver to translate a plain domain class into a {@link EntityPath}.
281287
* @param crudMethodMetadata metadata about the invoked CRUD methods.
282-
* @return
288+
* @return {@link RepositoryFragments} to be added to the repository.
283289
* @since 2.5.1
284290
*/
285291
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata, EntityManager entityManager,
286292
EntityPathResolver resolver, CrudMethodMetadata crudMethodMetadata) {
287293

288-
boolean isQueryDslRepository = QUERY_DSL_PRESENT
289-
&& QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
290-
291-
if (isQueryDslRepository) {
292-
293-
if (metadata.isReactiveRepository()) {
294-
throw new InvalidDataAccessApiUsageException(
295-
"Cannot combine Querydsl and reactive repository support in a single interface");
296-
}
297-
298-
QuerydslJpaPredicateExecutor<?> querydslJpaPredicateExecutor = new QuerydslJpaPredicateExecutor<>(
299-
getEntityInformation(metadata.getDomainType()), entityManager, resolver, crudMethodMetadata);
300-
invokeAwareMethods(querydslJpaPredicateExecutor);
294+
RepositoryFragments fragments = this.fragmentsContributor.contribute(metadata,
295+
getEntityInformation(metadata.getDomainType()), entityManager, resolver);
301296

302-
return RepositoryFragments
303-
.of(RepositoryFragment.implemented(QuerydslPredicateExecutor.class, querydslJpaPredicateExecutor));
297+
for (RepositoryFragment<?> fragment : fragments) {
298+
fragment.getImplementation().filter(JpaRepositoryConfigurationAware.class::isInstance)
299+
.ifPresent(it -> invokeAwareMethods((JpaRepositoryConfigurationAware) it));
304300
}
305301

306-
return RepositoryFragments.empty();
302+
return fragments;
307303
}
308304

309305
private void invokeAwareMethods(JpaRepositoryConfigurationAware repository) {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
5555
private @Nullable BeanFactory beanFactory;
5656
private @Nullable EntityManager entityManager;
5757
private EntityPathResolver entityPathResolver = SimpleEntityPathResolver.INSTANCE;
58+
private JpaRepositoryFragmentsContributor repositoryFragmentsContributor = JpaRepositoryFragmentsContributor.DEFAULT;
5859
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
5960
private @Nullable JpaQueryMethodFactory queryMethodFactory;
60-
private @Nullable Function<BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource;
61+
private @Nullable Function<@Nullable BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource;
6162

6263
/**
6364
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
@@ -100,20 +101,24 @@ public void setEntityPathResolver(ObjectProvider<EntityPathResolver> resolver) {
100101
this.entityPathResolver = resolver.getIfAvailable(() -> SimpleEntityPathResolver.INSTANCE);
101102
}
102103

104+
@Override
105+
public JpaRepositoryFragmentsContributor getRepositoryFragmentsContributor() {
106+
return repositoryFragmentsContributor;
107+
}
108+
103109
/**
104-
* Configures the {@link JpaQueryMethodFactory} to be used. Will expect a canonical bean to be present but will
105-
* fallback to {@link org.springframework.data.jpa.repository.query.DefaultJpaQueryMethodFactory} in case none is
106-
* available.
110+
* Configures the {@link JpaRepositoryFragmentsContributor} to contribute built-in fragment functionality to the
111+
* repository.
107112
*
108-
* @param resolver may be {@literal null}.
113+
* @param repositoryFragmentsContributor must not be {@literal null}.
114+
* @since 4.0
109115
*/
110-
@Autowired
111-
public void setQueryMethodFactory(ObjectProvider<JpaQueryMethodFactory> resolver) { // TODO: nullable insteand of ObjectProvider
116+
public void setRepositoryFragmentsContributor(JpaRepositoryFragmentsContributor repositoryFragmentsContributor) {
117+
this.repositoryFragmentsContributor = repositoryFragmentsContributor;
118+
}
112119

113-
JpaQueryMethodFactory factory = resolver.getIfAvailable();
114-
if (factory != null) {
115-
this.queryMethodFactory = factory;
116-
}
120+
public void setEscapeCharacter(char escapeCharacter) {
121+
this.escapeCharacter = EscapeCharacter.of(escapeCharacter);
117122
}
118123

119124
/**
@@ -153,6 +158,23 @@ public void setQueryEnhancerSelector(Class<? extends QueryEnhancerSelector> quer
153158
};
154159
}
155160

161+
/**
162+
* Configures the {@link JpaQueryMethodFactory} to be used. Will expect a canonical bean to be present but will
163+
* fallback to {@link org.springframework.data.jpa.repository.query.DefaultJpaQueryMethodFactory} in case none is
164+
* available.
165+
*
166+
* @param resolver may be {@literal null}.
167+
*/
168+
@Autowired
169+
public void setQueryMethodFactory(ObjectProvider<JpaQueryMethodFactory> resolver) { // TODO: nullable insteand of
170+
// ObjectProvider
171+
172+
JpaQueryMethodFactory factory = resolver.getIfAvailable();
173+
if (factory != null) {
174+
this.queryMethodFactory = factory;
175+
}
176+
}
177+
156178
@Override
157179
protected RepositoryFactorySupport doCreateRepositoryFactory() {
158180

@@ -169,6 +191,7 @@ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityM
169191
JpaRepositoryFactory factory = new JpaRepositoryFactory(entityManager);
170192
factory.setEntityPathResolver(entityPathResolver);
171193
factory.setEscapeCharacter(escapeCharacter);
194+
factory.setFragmentsContributor(getRepositoryFragmentsContributor());
172195

173196
if (queryMethodFactory != null) {
174197
factory.setQueryMethodFactory(queryMethodFactory);
@@ -189,8 +212,4 @@ public void afterPropertiesSet() {
189212
super.afterPropertiesSet();
190213
}
191214

192-
public void setEscapeCharacter(char escapeCharacter) {
193-
194-
this.escapeCharacter = EscapeCharacter.of(escapeCharacter);
195-
}
196215
}

0 commit comments

Comments
 (0)