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:
Mark Paluch
2021-10-22 09:11:41 +02:00
committed by Rossen Stoyanchev
parent eecddb314c
commit 7fea8730db
8 changed files with 191 additions and 287 deletions

View File

@@ -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"

View File

@@ -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'

View File

@@ -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'

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
});
}
}

View File

@@ -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;

View File

@@ -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> {
}