Migrate QuerydslDataFetcher to use fluent Querydsl API
Remove supporting classes for external projection as projections are handled by Spring Data directly. See gh-168
This commit is contained in:
committed by
Rossen Stoyanchev
parent
eecddb314c
commit
7fea8730db
@@ -59,7 +59,7 @@ configure(moduleProjects) {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.5"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.11"
|
||||
mavenBom "org.springframework:spring-framework-bom:5.3.10"
|
||||
mavenBom "org.springframework.data:spring-data-bom:2021.0.5"
|
||||
mavenBom "org.springframework.data:spring-data-bom:2021.1.0-RC1"
|
||||
mavenBom "org.springframework.security:spring-security-bom:5.5.2"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.31"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
|
||||
|
||||
@@ -13,6 +13,9 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
// TODO: Remove after upgrade to Spring Boot 2.6
|
||||
implementation 'org.springframework.data:spring-data-commons:2.6.0-RC1'
|
||||
implementation 'org.springframework.data:spring-data-jpa:2.6.0-RC1'
|
||||
implementation 'com.querydsl:querydsl-core'
|
||||
implementation 'com.querydsl:querydsl-jpa'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
@@ -31,7 +31,9 @@ dependencies {
|
||||
testImplementation 'org.springframework:spring-websocket'
|
||||
testImplementation 'org.springframework:spring-test'
|
||||
testImplementation 'org.springframework.data:spring-data-commons'
|
||||
testImplementation 'org.springframework.data:spring-data-keyvalue'
|
||||
testImplementation 'com.querydsl:querydsl-core'
|
||||
testImplementation 'com.querydsl:querydsl-collections'
|
||||
testImplementation 'javax.servlet:javax.servlet-api'
|
||||
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.graphql.data.querydsl;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.EntityInstantiator;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
|
||||
/**
|
||||
* {@link Converter} to instantiate DTOs from fully equipped domain objects.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DtoInstantiatingConverter<T> implements Converter<Object, T> {
|
||||
|
||||
private final Class<T> targetType;
|
||||
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
|
||||
|
||||
private final EntityInstantiator instantiator;
|
||||
|
||||
/**
|
||||
* Create a new {@link Converter} to instantiate DTOs.
|
||||
* @param dtoType target type
|
||||
* @param context mapping context to be used
|
||||
* @param entityInstantiators the instantiators to use for object creation
|
||||
*/
|
||||
public DtoInstantiatingConverter(Class<T> dtoType,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
|
||||
EntityInstantiators entityInstantiators) {
|
||||
|
||||
this.targetType = dtoType;
|
||||
this.context = context;
|
||||
this.instantiator = entityInstantiators.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T convert(Object source) {
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
return (T) source;
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> sourceEntity = this.context.getRequiredPersistentEntity(source.getClass());
|
||||
|
||||
PersistentPropertyAccessor<?> sourceAccessor = sourceEntity.getPropertyAccessor(source);
|
||||
PersistentEntity<?, ?> entity = this.context.getRequiredPersistentEntity(this.targetType);
|
||||
PreferredConstructor<?, ? extends PersistentProperty<?>> constructor = entity.getPersistenceConstructor();
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
Object dto = this.instantiator.createInstance(entity, new ParameterValueProvider() {
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(Parameter parameter) {
|
||||
return sourceAccessor.getProperty(
|
||||
sourceEntity.getRequiredPersistentProperty(parameter.getName()));
|
||||
}
|
||||
});
|
||||
|
||||
PersistentPropertyAccessor<?> dtoAccessor = entity.getPropertyAccessor(dto);
|
||||
|
||||
entity.doWithProperties((SimplePropertyHandler) property -> {
|
||||
|
||||
if (constructor.isConstructorParameter(property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dtoAccessor.setProperty(property,
|
||||
sourceAccessor.getProperty(sourceEntity.getRequiredPersistentProperty(property.getName())));
|
||||
});
|
||||
|
||||
return (T) dto;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.graphql.data.querydsl;
|
||||
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Lightweight {@link org.springframework.data.mapping.context.MappingContext}
|
||||
* to provide class metadata for entity to DTO mapping.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DtoMappingContext extends AbstractMappingContext<DtoMappingContext.DtoPersistentEntity<?>,
|
||||
DtoMappingContext.DtoPersistentProperty> {
|
||||
|
||||
@Override
|
||||
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
|
||||
// No Java std lib type introspection to not interfere with encapsulation.
|
||||
// We do not want to get into the business of materializing Java types.
|
||||
if (type.getType().getName().startsWith("java.") || type.getType().getName().startsWith("javax.")) {
|
||||
return false;
|
||||
}
|
||||
return super.shouldCreatePersistentEntityFor(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> DtoPersistentEntity<?> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
return new DtoPersistentEntity<>(typeInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DtoPersistentProperty createPersistentProperty(
|
||||
Property property, DtoPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
return new DtoPersistentProperty(property, owner, simpleTypeHolder);
|
||||
}
|
||||
|
||||
static class DtoPersistentEntity<T> extends BasicPersistentEntity<T, DtoPersistentProperty> {
|
||||
|
||||
public DtoPersistentEntity(TypeInformation<T> information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DtoPersistentProperty extends AnnotationBasedPersistentProperty<DtoPersistentProperty> {
|
||||
|
||||
public DtoPersistentProperty(
|
||||
Property property, PersistentEntity<?, DtoPersistentProperty> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
super(property, owner, simpleTypeHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Association<DtoPersistentProperty> createAssociation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.querydsl.core.BooleanBuilder;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import graphql.schema.DataFetcher;
|
||||
@@ -47,9 +46,7 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
@@ -60,8 +57,9 @@ import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -120,11 +118,12 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
private static final QuerydslPredicateBuilder BUILDER = new QuerydslPredicateBuilder(
|
||||
DefaultConversionService.getSharedInstance(), SimpleEntityPathResolver.INSTANCE);
|
||||
|
||||
private final TypeInformation<T> domainType;
|
||||
// visible to subtypes in the same package
|
||||
final TypeInformation<T> domainType;
|
||||
|
||||
private final QuerydslBinderCustomizer<EntityPath<?>> customizer;
|
||||
|
||||
QuerydslDataFetcher(ClassTypeInformation<T> domainType, QuerydslBinderCustomizer<EntityPath<?>> customizer) {
|
||||
QuerydslDataFetcher(TypeInformation<T> domainType, QuerydslBinderCustomizer<EntityPath<?>> customizer) {
|
||||
this.customizer = customizer;
|
||||
this.domainType = domainType;
|
||||
}
|
||||
@@ -140,10 +139,13 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public static <T> Builder<T, T> builder(QuerydslPredicateExecutor<T> executor) {
|
||||
Class<?> repositoryInterface = getRepositoryInterface(executor);
|
||||
DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
|
||||
Class<T> domainType = (Class<T>) metadata.getDomainType();
|
||||
|
||||
return new Builder<>(executor,
|
||||
(ClassTypeInformation<T>) ClassTypeInformation.from(metadata.getDomainType()),
|
||||
(bindings, root) -> {}, Function.identity());
|
||||
ClassTypeInformation.from(domainType),
|
||||
domainType,
|
||||
Sort.unsorted(),
|
||||
(bindings, root) -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,10 +159,13 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public static <T> ReactiveBuilder<T, T> builder(ReactiveQuerydslPredicateExecutor<T> executor) {
|
||||
Class<?> repositoryInterface = getRepositoryInterface(executor);
|
||||
DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
|
||||
Class<T> domainType = (Class<T>) metadata.getDomainType();
|
||||
|
||||
return new ReactiveBuilder<>(executor,
|
||||
(ClassTypeInformation<T>) ClassTypeInformation.from(metadata.getDomainType()),
|
||||
(bindings, root) -> {}, Function.identity());
|
||||
ClassTypeInformation.from(domainType),
|
||||
domainType,
|
||||
Sort.unsorted(),
|
||||
(bindings, root) -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,32 +197,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
parameters.put(entry.getKey(), Collections.singletonList(entry.getValue()));
|
||||
}
|
||||
|
||||
Predicate predicate = BUILDER.getPredicate(this.domainType, (MultiValueMap) parameters, bindings);
|
||||
|
||||
// Temporary workaround for this fix in Spring Data:
|
||||
// https://github.com/spring-projects/spring-data-commons/issues/2396
|
||||
|
||||
if (predicate == null) {
|
||||
predicate = new BooleanBuilder();
|
||||
}
|
||||
|
||||
return predicate;
|
||||
}
|
||||
|
||||
private static <S, T> Function<S, T> createProjection(Class<T> projectionType) {
|
||||
// TODO: SpelAwareProxyProjectionFactory, DtoMappingContext, and EntityInstantiators
|
||||
// should be reused to avoid duplicate class metadata.
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
|
||||
if (projectionType.isInterface()) {
|
||||
ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
return element -> projectionFactory.createProjection(projectionType, element);
|
||||
}
|
||||
|
||||
DtoInstantiatingConverter<T> converter = new DtoInstantiatingConverter<>(projectionType,
|
||||
new DtoMappingContext(), new EntityInstantiators());
|
||||
|
||||
return converter::convert;
|
||||
return BUILDER.getPredicate(this.domainType, (MultiValueMap) parameters, bindings);
|
||||
}
|
||||
|
||||
private static Class<?> getRepositoryInterface(Object executor) {
|
||||
@@ -251,18 +231,22 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final ClassTypeInformation<T> domainType;
|
||||
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
private final QuerydslBinderCustomizer<? extends EntityPath<T>> customizer;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
|
||||
Builder(QuerydslPredicateExecutor<T> executor, ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
this.customizer = customizer;
|
||||
this.resultConverter = resultConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +262,19 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public <P> Builder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new Builder<>(
|
||||
this.executor, this.domainType, this.customizer, createProjection(projectionType));
|
||||
this.executor, this.domainType, projectionType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Sort} order.
|
||||
* @param sort the default sort order
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
*/
|
||||
public Builder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new Builder<>(
|
||||
this.executor, this.domainType, this.resultType, sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +287,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public Builder<T, R> customizer(QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
|
||||
return new Builder<>(
|
||||
this.executor, this.domainType, customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,7 +296,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public DataFetcher<R> single() {
|
||||
return new SingleEntityFetcher<>(
|
||||
this.executor, this.domainType, this.customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +305,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public DataFetcher<Iterable<R>> many() {
|
||||
return new ManyEntityFetcher<>(
|
||||
this.executor, this.domainType, this.customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -325,21 +321,25 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final ReactiveQuerydslPredicateExecutor<T> executor;
|
||||
|
||||
private final ClassTypeInformation<T> domainType;
|
||||
private final TypeInformation<T> domainType;
|
||||
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
private final QuerydslBinderCustomizer<? extends EntityPath<T>> customizer;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
|
||||
ReactiveBuilder(ReactiveQuerydslPredicateExecutor<T> executor,
|
||||
ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
this.customizer = customizer;
|
||||
this.resultConverter = resultConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,7 +355,19 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public <P> ReactiveBuilder<T, P> projectAs(Class<P> projectionType) {
|
||||
Assert.notNull(projectionType, "Projection type must not be null");
|
||||
return new ReactiveBuilder<>(
|
||||
this.executor, this.domainType, this.customizer, createProjection(projectionType));
|
||||
this.executor, this.domainType, projectionType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Sort} order.
|
||||
* @param sort the default sort order
|
||||
* @return a new {@link Builder} instance with all previously configured
|
||||
* options and {@code Sort} applied
|
||||
*/
|
||||
public ReactiveBuilder<T, R> sortBy(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
return new ReactiveBuilder<>(
|
||||
this.executor, this.domainType, this.resultType, sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +380,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
public ReactiveBuilder<T, R> customizer(QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
Assert.notNull(customizer, "QuerydslBinderCustomizer must not be null");
|
||||
return new ReactiveBuilder<>(
|
||||
this.executor, this.domainType, customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,7 +389,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public DataFetcher<Mono<R>> single() {
|
||||
return new ReactiveSingleEntityFetcher<>(
|
||||
this.executor, this.domainType, this.customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,7 +398,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
*/
|
||||
public DataFetcher<Flux<R>> many() {
|
||||
return new ReactiveManyEntityFetcher<>(
|
||||
this.executor, this.domainType, this.customizer, this.resultConverter);
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.customizer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -395,24 +407,39 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final QuerydslPredicateExecutor<T> executor;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
SingleEntityFetcher(QuerydslPredicateExecutor<T> executor,
|
||||
ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
super(domainType, (QuerydslBinderCustomizer) customizer);
|
||||
this.executor = executor;
|
||||
this.resultConverter = resultConverter;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@SuppressWarnings({"ConstantConditions", "unchecked"})
|
||||
public R get(DataFetchingEnvironment environment) {
|
||||
Predicate predicate = buildPredicate(environment);
|
||||
return this.executor.findOne(predicate).map(this.resultConverter).orElse(null);
|
||||
return this.executor.findBy(buildPredicate(environment), q -> {
|
||||
FetchableFluentQuery<R> queryToUse = (FetchableFluentQuery<R>) q;
|
||||
|
||||
if(this.sort.isSorted()){
|
||||
queryToUse = queryToUse.sortBy(this.sort);
|
||||
}
|
||||
|
||||
if(!this.resultType.equals(this.domainType.getType())){
|
||||
queryToUse = queryToUse.as(this.resultType);
|
||||
}
|
||||
|
||||
return queryToUse.first();
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -421,22 +448,38 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final QuerydslPredicateExecutor<T> executor;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ManyEntityFetcher(QuerydslPredicateExecutor<T> executor,
|
||||
ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
super(domainType, (QuerydslBinderCustomizer) customizer);
|
||||
this.executor = executor;
|
||||
this.resultConverter = resultConverter;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Iterable<R> get(DataFetchingEnvironment environment) {
|
||||
Predicate predicate = buildPredicate(environment);
|
||||
return Streamable.of(this.executor.findAll(predicate)).map(this.resultConverter).toList();
|
||||
return this.executor.findBy(buildPredicate(environment), q -> {
|
||||
FetchableFluentQuery<R> queryToUse = (FetchableFluentQuery<R>) q;
|
||||
|
||||
if(this.sort.isSorted()){
|
||||
queryToUse = queryToUse.sortBy(this.sort);
|
||||
}
|
||||
|
||||
if(!this.resultType.equals(this.domainType.getType())){
|
||||
queryToUse = queryToUse.as(this.resultType);
|
||||
}
|
||||
|
||||
return queryToUse.all();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -445,22 +488,39 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final ReactiveQuerydslPredicateExecutor<T> executor;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ReactiveSingleEntityFetcher(ReactiveQuerydslPredicateExecutor<T> executor,
|
||||
ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
super(domainType, (QuerydslBinderCustomizer) customizer);
|
||||
this.executor = executor;
|
||||
this.resultConverter = resultConverter;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<R> get(DataFetchingEnvironment environment) {
|
||||
return this.executor.findOne(buildPredicate(environment)).map(this.resultConverter);
|
||||
return this.executor.findBy(buildPredicate(environment), q -> {
|
||||
FluentQuery.ReactiveFluentQuery<R> queryToUse = (FluentQuery.ReactiveFluentQuery<R>) q;
|
||||
|
||||
if(this.sort.isSorted()){
|
||||
queryToUse = queryToUse.sortBy(this.sort);
|
||||
}
|
||||
|
||||
if(!this.resultType.equals(this.domainType.getType())){
|
||||
queryToUse = queryToUse.as(this.resultType);
|
||||
}
|
||||
|
||||
return queryToUse.first();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -469,22 +529,39 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
private final ReactiveQuerydslPredicateExecutor<T> executor;
|
||||
|
||||
private final Function<T, R> resultConverter;
|
||||
private final Class<R> resultType;
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ReactiveManyEntityFetcher(ReactiveQuerydslPredicateExecutor<T> executor,
|
||||
ClassTypeInformation<T> domainType,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer,
|
||||
Function<T, R> resultConverter) {
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
QuerydslBinderCustomizer<? extends EntityPath<T>> customizer) {
|
||||
|
||||
super(domainType, (QuerydslBinderCustomizer) customizer);
|
||||
this.executor = executor;
|
||||
this.resultConverter = resultConverter;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Flux<R> get(DataFetchingEnvironment environment) {
|
||||
return this.executor.findAll(buildPredicate(environment)).map(this.resultConverter);
|
||||
return this.executor.findBy(buildPredicate(environment), q -> {
|
||||
FluentQuery.ReactiveFluentQuery<R> queryToUse = (FluentQuery.ReactiveFluentQuery<R>) q;
|
||||
|
||||
if(this.sort.isSorted()){
|
||||
queryToUse = queryToUse.sortBy(this.sort);
|
||||
}
|
||||
|
||||
if(!this.resultType.equals(this.domainType.getType())){
|
||||
queryToUse = queryToUse.as(this.resultType);
|
||||
}
|
||||
|
||||
return queryToUse.all();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package org.springframework.graphql.data.querydsl;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
public class Book {
|
||||
|
||||
Long id;
|
||||
@Id Long id;
|
||||
|
||||
String name;
|
||||
|
||||
|
||||
@@ -33,9 +33,13 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.keyvalue.core.KeyValueTemplate;
|
||||
import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory;
|
||||
import org.springframework.data.map.MapKeyValueAdapter;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
import org.springframework.graphql.execution.ExecutionGraphQlService;
|
||||
@@ -57,16 +61,18 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
class QuerydslDataFetcherTests {
|
||||
|
||||
private KeyValueRepositoryFactory repositoryFactory = new KeyValueRepositoryFactory(new KeyValueTemplate(new MapKeyValueAdapter()));
|
||||
private MockRepository mockRepository = repositoryFactory.getRepository(MockRepository.class);
|
||||
|
||||
@Test
|
||||
void shouldFetchSingleItems() {
|
||||
MockRepository mockRepository = mock(MockRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
when(mockRepository.findOne(any())).thenReturn(Optional.of(book));
|
||||
mockRepository.save(book);
|
||||
|
||||
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, QuerydslPredicateExecutor<?>> tester =
|
||||
(wiringConfigurer, executor) -> {
|
||||
WebGraphQlHandler handler = initWebGraphQlHandler(wiringConfigurer, executor, null);
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
|
||||
|
||||
// TODO: getData interferes with method overrides
|
||||
assertThat((Object) output.getData()).isEqualTo(
|
||||
@@ -85,10 +91,9 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
@Test
|
||||
void shouldFetchMultipleItems() {
|
||||
MockRepository mockRepository = mock(MockRepository.class);
|
||||
Book book1 = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
Book book2 = new Book(53L, "Breaking Bad", "Heisenberg");
|
||||
when(mockRepository.findAll(any(Predicate.class))).thenReturn(Arrays.asList(book1, book2));
|
||||
mockRepository.saveAll(Arrays.asList(book1, book2));
|
||||
|
||||
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, QuerydslPredicateExecutor<?>> tester =
|
||||
(wiringConfigurer, executor) -> {
|
||||
@@ -97,8 +102,8 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
assertThat((Object) output.getData()).isEqualTo(
|
||||
Collections.singletonMap("books", Arrays.asList(
|
||||
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"),
|
||||
Collections.singletonMap("name", "Breaking Bad"))));
|
||||
Collections.singletonMap("name", "Breaking Bad"),
|
||||
Collections.singletonMap("name", "Hitchhiker's Guide to the Galaxy"))));
|
||||
};
|
||||
|
||||
// explicit wiring
|
||||
@@ -114,7 +119,7 @@ class QuerydslDataFetcherTests {
|
||||
void shouldFavorExplicitWiring() {
|
||||
MockRepository mockRepository = mock(MockRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
when(mockRepository.findOne(any())).thenReturn(Optional.of(book));
|
||||
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
|
||||
|
||||
// 1) Automatic registration only
|
||||
WebGraphQlHandler handler = initWebGraphQlHandler(null, mockRepository, null);
|
||||
@@ -136,9 +141,8 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
@Test
|
||||
void shouldFetchSingleItemsWithInterfaceProjection() {
|
||||
MockRepository mockRepository = mock(MockRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
when(mockRepository.findOne(any())).thenReturn(Optional.of(book));
|
||||
mockRepository.save(book);
|
||||
|
||||
WebGraphQlHandler handler = initWebGraphQlHandler(builder -> builder
|
||||
.dataFetcher("bookById", QuerydslDataFetcher
|
||||
@@ -146,7 +150,7 @@ class QuerydslDataFetcherTests {
|
||||
.projectAs(BookProjection.class)
|
||||
.single()));
|
||||
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
|
||||
|
||||
assertThat((Object) output.getData()).isEqualTo(
|
||||
Collections.singletonMap("bookById",
|
||||
@@ -155,9 +159,8 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
@Test
|
||||
void shouldFetchSingleItemsWithDtoProjection() {
|
||||
MockRepository mockRepository = mock(MockRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
when(mockRepository.findOne(any())).thenReturn(Optional.of(book));
|
||||
mockRepository.save(book);
|
||||
|
||||
WebGraphQlHandler handler = initWebGraphQlHandler(builder -> builder
|
||||
.dataFetcher("bookById", QuerydslDataFetcher
|
||||
@@ -165,7 +168,7 @@ class QuerydslDataFetcherTests {
|
||||
.projectAs(BookDto.class)
|
||||
.single()));
|
||||
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 1) {name}}")).block();
|
||||
WebOutput output = handler.handleRequest(input("{ bookById(id: 42) {name}}")).block();
|
||||
|
||||
assertThat((Object) output.getData()).isEqualTo(
|
||||
Collections.singletonMap("bookById",
|
||||
@@ -187,7 +190,7 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
|
||||
ArgumentCaptor<Predicate> predicateCaptor = ArgumentCaptor.forClass(Predicate.class);
|
||||
verify(mockRepository).findAll(predicateCaptor.capture());
|
||||
verify(mockRepository).findBy(predicateCaptor.capture(), any());
|
||||
|
||||
Predicate predicate = predicateCaptor.getValue();
|
||||
assertThat(predicate).isEqualTo(QBook.book.name.startsWith("H").and(QBook.book.author.eq("Doug")));
|
||||
@@ -197,7 +200,7 @@ class QuerydslDataFetcherTests {
|
||||
void shouldReactivelyFetchSingleItems() {
|
||||
ReactiveMockRepository mockRepository = mock(ReactiveMockRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
when(mockRepository.findOne(any())).thenReturn(Mono.just(book));
|
||||
when(mockRepository.findBy(any(), any())).thenReturn(Mono.just(book));
|
||||
|
||||
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, ReactiveQuerydslPredicateExecutor<?>> tester =
|
||||
(wiringConfigurer, executor) -> {
|
||||
@@ -224,7 +227,7 @@ class QuerydslDataFetcherTests {
|
||||
ReactiveMockRepository mockRepository = mock(ReactiveMockRepository.class);
|
||||
Book book1 = new Book(42L, "Hitchhiker's Guide to the Galaxy", "Douglas Adams");
|
||||
Book book2 = new Book(53L, "Breaking Bad", "Heisenberg");
|
||||
when(mockRepository.findAll((Predicate) any())).thenReturn(Flux.just(book1, book2));
|
||||
when(mockRepository.findBy(any(), any())).thenReturn(Flux.just(book1, book2));
|
||||
|
||||
BiConsumer<Consumer<TypeRuntimeWiring.Builder>, ReactiveQuerydslPredicateExecutor<?>> tester =
|
||||
(wiringConfigurer, executor) -> {
|
||||
@@ -248,7 +251,7 @@ class QuerydslDataFetcherTests {
|
||||
|
||||
|
||||
@GraphQlRepository
|
||||
interface MockRepository extends Repository<Book, Long>, QuerydslPredicateExecutor<Book> {
|
||||
interface MockRepository extends CrudRepository<Book, Long>, QuerydslPredicateExecutor<Book> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user