Polishing contribution
Closes gh-201
This commit is contained in:
@@ -45,7 +45,6 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
@@ -87,35 +86,65 @@ import org.springframework.util.StringUtils;
|
||||
* wiring.dataFetcher("book", QueryByExampleDataFetcher.builder(repository).single());
|
||||
* </pre>
|
||||
*
|
||||
* <p>See methods on {@link QueryByExampleDataFetcher.Builder} and {@link QueryByExampleDataFetcher.ReactiveBuilder} for further
|
||||
* options on GraphQL Query argument to Query by Example bindings, result projections, and sorting.
|
||||
* <p>See methods on {@link QueryByExampleDataFetcher.Builder} and
|
||||
* {@link QueryByExampleDataFetcher.ReactiveBuilder} for further options on
|
||||
* GraphQL Query argument to Query by Example bindings, result projections, and
|
||||
* sorting.
|
||||
*
|
||||
* @param <T> returned result type
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @see QueryByExampleExecutor
|
||||
* @see ReactiveQueryByExampleExecutor
|
||||
* @see Example
|
||||
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#query-by-example">
|
||||
* Spring Data Query By Example extension</a>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final TypeInformation<T> domainType;
|
||||
|
||||
private GraphQlArgumentInstantiator instantiator;
|
||||
private final GraphQlArgumentInstantiator instantiator;
|
||||
|
||||
QueryByExampleDataFetcher(TypeInformation<T> domainType, @Nullable ConversionService conversionService) {
|
||||
|
||||
QueryByExampleDataFetcher(TypeInformation<T> domainType) {
|
||||
this.domainType = domainType;
|
||||
this.instantiator = new GraphQlArgumentInstantiator(conversionService);
|
||||
this.instantiator = new GraphQlArgumentInstantiator(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare an {@link Example} from GraphQL query arguments.
|
||||
* @param env contextual info for the GraphQL query
|
||||
* @return the resulting example
|
||||
*/
|
||||
protected Example<T> buildExample(DataFetchingEnvironment env) {
|
||||
return Example.of(this.instantiator.instantiate(env.getArguments(), this.domainType.getType()));
|
||||
}
|
||||
|
||||
protected boolean requiresProjection(Class<?> resultType) {
|
||||
return !resultType.equals(this.domainType.getType());
|
||||
}
|
||||
|
||||
protected Collection<String> buildPropertyPaths(DataFetchingFieldSelectionSet selection, Class<?> resultType) {
|
||||
|
||||
// Compute selection only for non-projections
|
||||
if (this.domainType.getType().equals(resultType) ||
|
||||
this.domainType.getType().isAssignableFrom(resultType) ||
|
||||
this.domainType.isSubTypeOf(resultType)) {
|
||||
return PropertySelection.create(this.domainType, selection).toList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder} accepting {@link QueryByExampleExecutor}
|
||||
* to build a {@link DataFetcher}.
|
||||
*
|
||||
* @param executor the repository object to use
|
||||
* @param <T> result type
|
||||
* @param executor the QBE repository object to use
|
||||
* @param <T> the domain type of the repository
|
||||
* @return a new builder
|
||||
*/
|
||||
public static <T> Builder<T, T> builder(QueryByExampleExecutor<T> executor) {
|
||||
@@ -124,10 +153,10 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveBuilder} accepting
|
||||
* {@link ReactiveQueryByExampleExecutor} to build a reactive {@link DataFetcher}.
|
||||
* {@link ReactiveQueryByExampleExecutor} to build a {@link DataFetcher}.
|
||||
*
|
||||
* @param executor the repository object to use
|
||||
* @param <T> result type
|
||||
* @param executor the QBE repository object to use
|
||||
* @param <T> the domain type of the repository
|
||||
* @return a new builder
|
||||
*/
|
||||
public static <T> ReactiveBuilder<T, T> builder(ReactiveQueryByExampleExecutor<T> executor) {
|
||||
@@ -177,31 +206,6 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
String.format("Cannot resolve repository interface from %s", executor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an {@link Example} from GraphQL query arguments.
|
||||
*
|
||||
* @param environment contextual info for the GraphQL query
|
||||
* @return the resulting example
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected Example<T> buildExample(DataFetchingEnvironment environment) {
|
||||
return Example.of(instantiator.instantiate(environment.getArguments(), domainType.getType()));
|
||||
}
|
||||
|
||||
protected boolean requiresProjection(Class<?> resultType) {
|
||||
return !resultType.equals(this.domainType.getType());
|
||||
}
|
||||
|
||||
protected Collection<String> buildPropertyPaths(DataFetchingFieldSelectionSet selection, Class<?> resultType) {
|
||||
|
||||
// Compute selection only for non-projections
|
||||
if (this.domainType.getType().equals(resultType) ||
|
||||
this.domainType.getType().isAssignableFrom(resultType) ||
|
||||
this.domainType.isSubTypeOf(resultType)) {
|
||||
return PropertySelection.create(this.domainType, selection).toList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for a Query by Example-based {@link DataFetcher}. Note that builder
|
||||
@@ -221,78 +225,64 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Builder(QueryByExampleExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor,
|
||||
ClassTypeInformation.from((Class<T>) domainType),
|
||||
domainType,
|
||||
Sort.unsorted(),
|
||||
null);
|
||||
this(executor, ClassTypeInformation.from((Class<T>) domainType), domainType, Sort.unsorted());
|
||||
}
|
||||
|
||||
Builder(QueryByExampleExecutor<T> executor, ClassTypeInformation<T> domainType, Class<R> resultType, Sort sort, ConversionService conversionService) {
|
||||
Builder(QueryByExampleExecutor<T> executor, ClassTypeInformation<T> domainType, Class<R> resultType, Sort sort) {
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project results returned from the {@link QueryByExampleExecutor}
|
||||
* into the target {@code projectionType}. Projection types can be
|
||||
* either interfaces declaring getters for properties to expose or
|
||||
* regular classes outside the entity type hierarchy for
|
||||
* DTO projection.
|
||||
*
|
||||
* either interfaces with property getters to expose or regular classes
|
||||
* outside the entity type hierarchy for DTO projections.
|
||||
* @param projectionType projection type
|
||||
* @return a new {@link Builder} instance with all previously
|
||||
* configured options and {@code projectionType} applied
|
||||
*/
|
||||
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, projectionType, this.sort, this.conversionService);
|
||||
return new Builder<>(this.executor, this.domainType, projectionType, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, this.conversionService);
|
||||
return new Builder<>(this.executor, this.domainType, this.resultType, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch single object instances.
|
||||
*
|
||||
* @return a {@link DataFetcher} based on Query by Example to fetch one object
|
||||
*/
|
||||
public DataFetcher<R> single() {
|
||||
return new SingleEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
|
||||
return new SingleEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch many object instances.
|
||||
*
|
||||
* @return a {@link DataFetcher} based on Query Example to fetch many objects
|
||||
*/
|
||||
public DataFetcher<Iterable<R>> many() {
|
||||
return new ManyEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
|
||||
return new ManyEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for a reactive Query by Example-based {@link DataFetcher}. Note that builder
|
||||
* instances are immutable and return a new instance of the builder when
|
||||
* calling configuration methods.
|
||||
* Builder for a reactive Query by Example-based {@link DataFetcher}.
|
||||
* Note that builder instances are immutable and return a new instance of
|
||||
* the builder when calling configuration methods.
|
||||
*
|
||||
* @param <T> domain type
|
||||
* @param <R> result type
|
||||
@@ -307,78 +297,63 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
private final Sort sort;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor, Class<R> domainType) {
|
||||
this(executor,
|
||||
ClassTypeInformation.from((Class<T>) domainType),
|
||||
domainType,
|
||||
Sort.unsorted(),
|
||||
null);
|
||||
this(executor, ClassTypeInformation.from((Class<T>) domainType), domainType, Sort.unsorted());
|
||||
}
|
||||
|
||||
ReactiveBuilder(ReactiveQueryByExampleExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
ConversionService conversionService) {
|
||||
ReactiveBuilder(
|
||||
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
|
||||
Class<R> resultType, Sort sort) {
|
||||
|
||||
this.executor = executor;
|
||||
this.domainType = domainType;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project results returned from the {@link QueryByExampleExecutor}
|
||||
* Project results returned from the {@link ReactiveQueryByExampleExecutor}
|
||||
* into the target {@code projectionType}. Projection types can be
|
||||
* either interfaces declaring getters for properties to expose or
|
||||
* regular classes outside the entity type hierarchy for
|
||||
* DTO projection.
|
||||
*
|
||||
* either interfaces with property getters to expose or regular classes
|
||||
* outside the entity type hierarchy for DTO projections.
|
||||
* @param projectionType projection type
|
||||
* @return a new {@link ReactiveBuilder} instance with all previously
|
||||
* configured options and {@code projectionType} applied
|
||||
*/
|
||||
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, projectionType, this.sort, this.conversionService);
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, projectionType, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a {@link Sort} order.
|
||||
*
|
||||
* @param sort the default sort order
|
||||
* @return a new {@link ReactiveBuilder} 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, this.conversionService);
|
||||
return new ReactiveBuilder<>(this.executor, this.domainType, this.resultType, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch single object instances through {@link Mono}.
|
||||
*
|
||||
* @return a {@link DataFetcher} based on Query by Example to fetch one object
|
||||
* Build a {@link DataFetcher} to fetch single object instances.
|
||||
*/
|
||||
public DataFetcher<Mono<R>> single() {
|
||||
return new ReactiveSingleEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
|
||||
return new ReactiveSingleEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch many object instances through {@link Flux}.
|
||||
*
|
||||
* @return a {@link DataFetcher} based on Query by Example to fetch many objects
|
||||
* Build a {@link DataFetcher} to fetch many object instances.
|
||||
*/
|
||||
public DataFetcher<Flux<R>> many() {
|
||||
return new ReactiveManyEntityFetcher<>(
|
||||
this.executor, this.domainType, this.resultType, this.sort, this.conversionService);
|
||||
return new ReactiveManyEntityFetcher<>(this.executor, this.domainType, this.resultType, this.sort);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class SingleEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<R> {
|
||||
|
||||
private final QueryByExampleExecutor<T> executor;
|
||||
@@ -388,13 +363,10 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
SingleEntityFetcher(QueryByExampleExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
ConversionService conversionService) {
|
||||
SingleEntityFetcher(
|
||||
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType, Class<R> resultType, Sort sort) {
|
||||
|
||||
super(domainType, conversionService);
|
||||
super(domainType);
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
@@ -424,6 +396,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ManyEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Iterable<R>> {
|
||||
|
||||
private final QueryByExampleExecutor<T> executor;
|
||||
@@ -433,12 +406,11 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ManyEntityFetcher(QueryByExampleExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
ConversionService conversionService) {
|
||||
super(domainType, conversionService);
|
||||
ManyEntityFetcher(
|
||||
QueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
|
||||
Class<R> resultType, Sort sort) {
|
||||
|
||||
super(domainType);
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
@@ -467,6 +439,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ReactiveSingleEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Mono<R>> {
|
||||
|
||||
private final ReactiveQueryByExampleExecutor<T> executor;
|
||||
@@ -476,13 +449,11 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ReactiveSingleEntityFetcher(ReactiveQueryByExampleExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
ConversionService conversionService) {
|
||||
ReactiveSingleEntityFetcher(
|
||||
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
|
||||
Class<R> resultType, Sort sort) {
|
||||
|
||||
super(domainType, conversionService);
|
||||
super(domainType);
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
@@ -511,6 +482,7 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class ReactiveManyEntityFetcher<T, R> extends QueryByExampleDataFetcher<T> implements DataFetcher<Flux<R>> {
|
||||
|
||||
private final ReactiveQueryByExampleExecutor<T> executor;
|
||||
@@ -520,13 +492,11 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
private final Sort sort;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
ReactiveManyEntityFetcher(ReactiveQueryByExampleExecutor<T> executor,
|
||||
TypeInformation<T> domainType,
|
||||
Class<R> resultType,
|
||||
Sort sort,
|
||||
ConversionService conversionService) {
|
||||
ReactiveManyEntityFetcher(
|
||||
ReactiveQueryByExampleExecutor<T> executor, TypeInformation<T> domainType,
|
||||
Class<R> resultType, Sort sort) {
|
||||
|
||||
super(domainType, conversionService);
|
||||
super(domainType);
|
||||
this.executor = executor;
|
||||
this.resultType = resultType;
|
||||
this.sort = sort;
|
||||
@@ -552,8 +522,10 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
return queryToUse.all();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GraphQLTypeVisitor that auto-registers Query By Example Spring Data repositories.
|
||||
*/
|
||||
@@ -659,4 +631,5 @@ public abstract class QueryByExampleDataFetcher<T> {
|
||||
return (fetcher != null && !(fetcher instanceof PropertyDataFetcher));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for {@link graphql.schema.DataFetcher}s backed by Querydsl based
|
||||
* Spring Data repositories.
|
||||
* {@link graphql.schema.DataFetcher} implementations built on the Spring Data,
|
||||
* Query by Example support.
|
||||
*
|
||||
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#query-by-example">
|
||||
* Spring Data Query By Example extension</a>
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
|
||||
@@ -49,6 +49,7 @@ 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.repository.query.QueryByExampleExecutor;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
@@ -255,11 +256,10 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Project results returned from the {@link QuerydslPredicateExecutor}
|
||||
* Project results returned from the {@link QueryByExampleExecutor}
|
||||
* into the target {@code projectionType}. Projection types can be
|
||||
* either interfaces declaring getters for properties to expose or
|
||||
* regular classes outside the entity type hierarchy for
|
||||
* DTO projection.
|
||||
* either interfaces with property getters to expose or regular classes
|
||||
* outside the entity type hierarchy for DTO projections.
|
||||
* @param projectionType projection type
|
||||
* @return a new {@link Builder} instance with all previously
|
||||
* configured options and {@code projectionType} applied
|
||||
@@ -293,7 +293,6 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch single object instances.
|
||||
* @return a {@link DataFetcher} based on Querydsl to fetch one object
|
||||
*/
|
||||
public DataFetcher<R> single() {
|
||||
return new SingleEntityFetcher<>(
|
||||
@@ -302,7 +301,6 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch many object instances.
|
||||
* @return a {@link DataFetcher} based on Querydsl to fetch many objects
|
||||
*/
|
||||
public DataFetcher<Iterable<R>> many() {
|
||||
return new ManyEntityFetcher<>(
|
||||
@@ -354,11 +352,10 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Project results returned from the {@link QuerydslPredicateExecutor}
|
||||
* Project results returned from the {@link ReactiveQueryByExampleExecutor}
|
||||
* into the target {@code projectionType}. Projection types can be
|
||||
* either interfaces declaring getters for properties to expose or
|
||||
* regular classes outside the entity type hierarchy for
|
||||
* DTO projection.
|
||||
* either interfaces with property getters to expose or regular classes
|
||||
* outside the entity type hierarchy for DTO projections.
|
||||
* @param projectionType projection type
|
||||
* @return a new {@link Builder} instance with all previously
|
||||
* configured options and {@code projectionType} applied
|
||||
@@ -391,8 +388,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch single object instances through {@link Mono}.
|
||||
* @return a {@link DataFetcher} based on Querydsl to fetch one object
|
||||
* Build a {@link DataFetcher} to fetch single object instances}.
|
||||
*/
|
||||
public DataFetcher<Mono<R>> single() {
|
||||
return new ReactiveSingleEntityFetcher<>(
|
||||
@@ -400,8 +396,7 @@ public abstract class QuerydslDataFetcher<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link DataFetcher} to fetch many object instances through {@link Flux}.
|
||||
* @return a {@link DataFetcher} based on Querydsl to fetch many objects
|
||||
* Build a {@link DataFetcher} to fetch many object instances.
|
||||
*/
|
||||
public DataFetcher<Flux<R>> many() {
|
||||
return new ReactiveManyEntityFetcher<>(
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for {@link graphql.schema.DataFetcher}s backed by Querydsl based
|
||||
* Spring Data repositories.
|
||||
* {@link graphql.schema.DataFetcher} implementations built on Querydsl, Spring
|
||||
* Data repositories.
|
||||
*
|
||||
* @see <a href="https://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.extensions.querydsl">
|
||||
* Spring Data Querydsl extension</a>
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
|
||||
@@ -4,5 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
|
||||
@GraphQlRepository
|
||||
public interface BookRepository extends JpaRepository<Book, Long> {
|
||||
public interface BookJpaRepository extends JpaRepository<Book, Long> {
|
||||
}
|
||||
@@ -70,27 +70,8 @@ import static org.mockito.Mockito.when;
|
||||
class QueryByExampleDataFetcherJpaTests {
|
||||
|
||||
@Autowired
|
||||
private BookRepository repository;
|
||||
private BookJpaRepository repository;
|
||||
|
||||
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(
|
||||
@Nullable QueryByExampleExecutor<?> executor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
executor != null
|
||||
? Collections.singletonList(executor)
|
||||
: Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchSingleItems() {
|
||||
@@ -137,7 +118,7 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
|
||||
@Test
|
||||
void shouldFavorExplicitWiring() {
|
||||
BookRepository mockRepository = mock(BookRepository.class);
|
||||
BookJpaRepository mockRepository = mock(BookJpaRepository.class);
|
||||
Book book = new Book(42L, "Hitchhiker's Guide to the Galaxy", new Author(0L, "Douglas", "Adams"));
|
||||
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
|
||||
|
||||
@@ -188,6 +169,23 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
executor != null ? Collections.singletonList(executor) : Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
private WebInput input(String query) {
|
||||
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
|
||||
}
|
||||
@@ -200,6 +198,7 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class BookDto {
|
||||
|
||||
private final String name;
|
||||
@@ -214,6 +213,7 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories(considerNestedRepositories = true)
|
||||
static class TestConfig {
|
||||
@@ -246,4 +246,5 @@ class QueryByExampleDataFetcherJpaTests {
|
||||
return transactionManager;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
|
||||
@GraphQlRepository
|
||||
public interface BookRepository extends MongoRepository<Book, String> {
|
||||
public interface BookMongoRepository extends MongoRepository<Book, String> {
|
||||
}
|
||||
@@ -4,5 +4,5 @@ import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import org.springframework.graphql.data.GraphQlRepository;
|
||||
|
||||
@GraphQlRepository
|
||||
public interface ReactiveBookRepository extends ReactiveMongoRepository<Book, String> {
|
||||
public interface BookReactiveMongoRepository extends ReactiveMongoRepository<Book, String> {
|
||||
}
|
||||
@@ -64,32 +64,15 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class QueryByExampleDataFetcherMongoDbTests {
|
||||
|
||||
@Container
|
||||
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
|
||||
|
||||
@Autowired
|
||||
private BookRepository repository;
|
||||
private BookMongoRepository repository;
|
||||
|
||||
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(
|
||||
@Nullable QueryByExampleExecutor<?> executor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
|
||||
Collections.emptyList());
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchSingleItems() {
|
||||
@@ -134,7 +117,7 @@ class QueryByExampleDataFetcherMongoDbTests {
|
||||
|
||||
@Test
|
||||
void shouldFavorExplicitWiring() {
|
||||
BookRepository mockRepository = mock(BookRepository.class);
|
||||
BookMongoRepository mockRepository = mock(BookMongoRepository.class);
|
||||
Book book = new Book("42", "Hitchhiker's Guide to the Galaxy", new Author("0", "Douglas", "Adams"));
|
||||
when(mockRepository.findBy(any(), any())).thenReturn(Optional.of(book));
|
||||
|
||||
@@ -184,16 +167,35 @@ class QueryByExampleDataFetcherMongoDbTests {
|
||||
assertThat(actualBook.getName()).isEqualTo("The book is: Hitchhiker's Guide to the Galaxy");
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(@Nullable QueryByExampleExecutor<?> executor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()),
|
||||
Collections.emptyList());
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
private WebInput input(String query) {
|
||||
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
|
||||
}
|
||||
|
||||
|
||||
interface BookProjection {
|
||||
|
||||
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
|
||||
String getName();
|
||||
}
|
||||
|
||||
|
||||
static class BookDto {
|
||||
|
||||
private final String name;
|
||||
@@ -207,6 +209,7 @@ class QueryByExampleDataFetcherMongoDbTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableMongoRepositories(considerNestedRepositories = true)
|
||||
static class TestConfig {
|
||||
@@ -215,8 +218,8 @@ class QueryByExampleDataFetcherMongoDbTests {
|
||||
MongoTemplate mongoTemplate() {
|
||||
return new MongoTemplate(MongoClients.create(String.format("mongodb://%s:%d",
|
||||
mongoDBContainer.getContainerIpAddress(),
|
||||
mongoDBContainer.getFirstMappedPort())),
|
||||
"test");
|
||||
mongoDBContainer.getFirstMappedPort())), "test");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,32 +60,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@Testcontainers
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class QueryByExampleDataFetcherReactiveMongoDbTests {
|
||||
|
||||
@Container
|
||||
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
|
||||
|
||||
@Autowired
|
||||
private ReactiveBookRepository repository;
|
||||
private BookReactiveMongoRepository repository;
|
||||
|
||||
static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
static GraphQlSetup graphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(
|
||||
@Nullable ReactiveQueryByExampleExecutor<?> reactiveExecutor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
Collections.emptyList(),
|
||||
(reactiveExecutor != null ? Collections.singletonList(reactiveExecutor) : Collections.emptyList()));
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReactivelyFetchSingleItems() {
|
||||
@@ -156,16 +139,36 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
|
||||
tester.accept(graphQlSetup(repository));
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(String fieldName, DataFetcher<?> fetcher) {
|
||||
return initGraphQlSetup(null).queryFetcher(fieldName, fetcher);
|
||||
}
|
||||
|
||||
private static GraphQlSetup graphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
|
||||
return initGraphQlSetup(executor);
|
||||
}
|
||||
|
||||
private static GraphQlSetup initGraphQlSetup(@Nullable ReactiveQueryByExampleExecutor<?> executor) {
|
||||
|
||||
GraphQLTypeVisitor visitor = QueryByExampleDataFetcher.registrationTypeVisitor(
|
||||
Collections.emptyList(),
|
||||
(executor != null ? Collections.singletonList(executor) : Collections.emptyList()));
|
||||
|
||||
return GraphQlSetup.schemaResource(BookSource.schema).typeVisitor(visitor);
|
||||
}
|
||||
|
||||
private WebInput input(String query) {
|
||||
return new WebInput(URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", query), null, "1");
|
||||
}
|
||||
|
||||
|
||||
interface BookProjection {
|
||||
|
||||
@Value("#{target.name + ' by ' + target.author.firstName + ' ' + target.author.lastName}")
|
||||
String getName();
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class BookDto {
|
||||
|
||||
private final String name;
|
||||
@@ -177,8 +180,10 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
|
||||
public String getName() {
|
||||
return "The book is: " + name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableReactiveMongoRepositories(considerNestedRepositories = true)
|
||||
static class TestConfig {
|
||||
@@ -190,5 +195,7 @@ class QueryByExampleDataFetcherReactiveMongoDbTests {
|
||||
mongoDBContainer.getFirstMappedPort())),
|
||||
"test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user