Add fluent API support for QuerydslKeyValueRepository.
Also reuse ProjectionFactory from repository factory. Closes #397.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 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.data.keyvalue.repository.support;
|
||||
|
||||
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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link Converter} to instantiate DTOs from fully equipped domain objects.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6
|
||||
*/
|
||||
class DtoInstantiatingConverter implements Converter<Object, Object> {
|
||||
|
||||
private final Class<?> targetType;
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
|
||||
private final EntityInstantiator instantiator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Converter} to instantiate DTOs.
|
||||
*
|
||||
* @param dtoType must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param entityInstantiators must not be {@literal null}.
|
||||
*/
|
||||
public DtoInstantiatingConverter(Class<?> dtoType,
|
||||
MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context,
|
||||
EntityInstantiators entityInstantiators) {
|
||||
|
||||
Assert.notNull(dtoType, "DTO type must not be null!");
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
Assert.notNull(entityInstantiators, "EntityInstantiators must not be null!");
|
||||
|
||||
this.targetType = dtoType;
|
||||
this.context = context;
|
||||
this.instantiator = entityInstantiators.getInstantiatorFor(context.getRequiredPersistentEntity(dtoType));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public Object convert(Object source) {
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> sourceEntity = context.getRequiredPersistentEntity(source.getClass());
|
||||
PersistentPropertyAccessor<?> sourceAccessor = sourceEntity.getPropertyAccessor(source);
|
||||
PersistentEntity<?, ?> targetEntity = context.getRequiredPersistentEntity(targetType);
|
||||
PreferredConstructor<?, ? extends PersistentProperty<?>> constructor = targetEntity.getPersistenceConstructor();
|
||||
|
||||
Object dto = instantiator.createInstance(targetEntity, new ParameterValueProvider() {
|
||||
|
||||
@Override
|
||||
public Object getParameterValue(Parameter parameter) {
|
||||
return sourceAccessor.getProperty(sourceEntity.getPersistentProperty(parameter.getName()));
|
||||
}
|
||||
});
|
||||
|
||||
PersistentPropertyAccessor<?> dtoAccessor = targetEntity.getPropertyAccessor(dto);
|
||||
|
||||
targetEntity.doWithProperties((SimplePropertyHandler) property -> {
|
||||
|
||||
if (constructor.isConstructorParameter(property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dtoAccessor.setProperty(property,
|
||||
sourceAccessor.getProperty(sourceEntity.getPersistentProperty(property.getName())));
|
||||
});
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
@@ -152,12 +153,12 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
/**
|
||||
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add Key-Value-specific extensions.
|
||||
* Typically adds a {@link QuerydslMongoPredicateExecutor} if the repository interface uses Querydsl.
|
||||
* Typically adds a {@link QuerydslKeyValuePredicateExecutor} if the repository interface uses Querydsl.
|
||||
* <p>
|
||||
* Can be overridden by subclasses to customize {@link RepositoryFragments}.
|
||||
*
|
||||
* @param metadata repository metadata.
|
||||
* @param operations the MongoDB operations manager.
|
||||
* @param operations the KeyValue operations manager.
|
||||
* @return
|
||||
* @since 2.6
|
||||
*/
|
||||
@@ -171,7 +172,8 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
|
||||
return RepositoryFragments
|
||||
.just(new QuerydslKeyValuePredicateExecutor<>(getEntityInformation(metadata.getDomainType()), operations));
|
||||
.just(new QuerydslKeyValuePredicateExecutor<>(getEntityInformation(metadata.getDomainType()),
|
||||
getProjectionFactory(), operations, SimpleEntityPathResolver.INSTANCE));
|
||||
}
|
||||
|
||||
return RepositoryFragments.empty();
|
||||
|
||||
@@ -17,9 +17,14 @@ package org.springframework.data.keyvalue.repository.support;
|
||||
|
||||
import static org.springframework.data.keyvalue.repository.support.KeyValueQuerydslUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -27,6 +32,12 @@ import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.querydsl.EntityPathResolver;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
@@ -38,6 +49,7 @@ import org.springframework.util.Assert;
|
||||
import com.querydsl.collections.AbstractCollQuery;
|
||||
import com.querydsl.collections.CollQuery;
|
||||
import com.querydsl.core.NonUniqueResultException;
|
||||
import com.querydsl.core.QueryResults;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
@@ -53,8 +65,12 @@ public class QuerydslKeyValuePredicateExecutor<T> implements QuerydslPredicateEx
|
||||
|
||||
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
|
||||
|
||||
private final MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> context;
|
||||
private final PathBuilder<T> builder;
|
||||
private final Supplier<Iterable<T>> findAll;
|
||||
private final EntityInformation<T, ?> entityInformation;
|
||||
private final ProjectionFactory projectionFactory;
|
||||
private final EntityInstantiators entityInstantiators = new EntityInstantiators();
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslKeyValuePredicateExecutor} for the given {@link EntityInformation}.
|
||||
@@ -63,7 +79,7 @@ public class QuerydslKeyValuePredicateExecutor<T> implements QuerydslPredicateEx
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
public QuerydslKeyValuePredicateExecutor(EntityInformation<T, ?> entityInformation, KeyValueOperations operations) {
|
||||
this(entityInformation, operations, DEFAULT_ENTITY_PATH_RESOLVER);
|
||||
this(entityInformation, new SpelAwareProxyProjectionFactory(), operations, DEFAULT_ENTITY_PATH_RESOLVER);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,16 +87,24 @@ public class QuerydslKeyValuePredicateExecutor<T> implements QuerydslPredicateEx
|
||||
* {@link EntityPathResolver}.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param resolver must not be {@literal null}.
|
||||
*/
|
||||
public QuerydslKeyValuePredicateExecutor(EntityInformation<T, ?> entityInformation, KeyValueOperations operations,
|
||||
public QuerydslKeyValuePredicateExecutor(EntityInformation<T, ?> entityInformation,
|
||||
ProjectionFactory projectionFactory, KeyValueOperations operations,
|
||||
EntityPathResolver resolver) {
|
||||
|
||||
Assert.notNull(entityInformation, "EntityInformation must not be null!");
|
||||
Assert.notNull(projectionFactory, "ProjectionFactory must not be null!");
|
||||
Assert.notNull(operations, "KeyValueOperations must not be null!");
|
||||
Assert.notNull(resolver, "EntityPathResolver must not be null!");
|
||||
|
||||
this.projectionFactory = projectionFactory;
|
||||
this.context = operations.getMappingContext();
|
||||
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
|
||||
this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
|
||||
this.entityInformation = entityInformation;
|
||||
findAll = () -> operations.findAll(entityInformation.getJavaType());
|
||||
}
|
||||
|
||||
@@ -209,10 +233,19 @@ public class QuerydslKeyValuePredicateExecutor<T> implements QuerydslPredicateEx
|
||||
return count(predicate) > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends T, R> R findBy(Predicate predicate,
|
||||
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
throw new UnsupportedOperationException("Not yet supported");
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(queryFunction, "Query function must not be null!");
|
||||
|
||||
return queryFunction.apply(new FluentQuerydsl<>(predicate, (Class<S>) entityInformation.getJavaType()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,4 +261,219 @@ public class QuerydslKeyValuePredicateExecutor<T> implements QuerydslPredicateEx
|
||||
|
||||
return predicate != null ? query.where(predicate) : query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using Querydsl
|
||||
* {@link Predicate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6
|
||||
*/
|
||||
class FluentQuerydsl<R> implements FluentQuery.FetchableFluentQuery<R> {
|
||||
|
||||
private final Predicate predicate;
|
||||
private final Sort sort;
|
||||
private final Class<?> entityType;
|
||||
private final Class<R> resultType;
|
||||
private final List<String> fieldsToInclude;
|
||||
|
||||
FluentQuerydsl(Predicate predicate, Class<R> resultType) {
|
||||
this(predicate, Sort.unsorted(), resultType, resultType, Collections.emptyList());
|
||||
}
|
||||
|
||||
public FluentQuerydsl(Predicate predicate, Sort sort, Class<?> entityType, Class<R> resultType,
|
||||
List<String> fieldsToInclude) {
|
||||
this.predicate = predicate;
|
||||
this.sort = sort;
|
||||
this.entityType = entityType;
|
||||
this.resultType = resultType;
|
||||
this.fieldsToInclude = fieldsToInclude;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#sortBy(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public FluentQuery.FetchableFluentQuery<R> sortBy(Sort sort) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
return new FluentQuerydsl<>(predicate, sort, entityType, resultType, fieldsToInclude);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <NR> FluentQuery.FetchableFluentQuery<NR> as(Class<NR> projection) {
|
||||
|
||||
Assert.notNull(projection, "Projection target type must not be null!");
|
||||
|
||||
return new FluentQuerydsl<>(predicate, sort, entityType, projection, fieldsToInclude);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#project(java.util.Collection)
|
||||
*/
|
||||
public FluentQuery.FetchableFluentQuery<R> project(Collection<String> properties) {
|
||||
|
||||
Assert.notNull(properties, "Projection properties must not be null!");
|
||||
|
||||
return new FluentQuerydsl<>(predicate, sort, entityType, resultType, new ArrayList<>(properties));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
|
||||
*/
|
||||
@Override
|
||||
public R oneValue() {
|
||||
|
||||
List<T> results = createQuery().limit(2).fetch();
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (results.size() > 1) {
|
||||
throw new IncorrectResultSizeDataAccessException(1);
|
||||
}
|
||||
|
||||
T one = results.get(0);
|
||||
return getConversionFunction().apply(one);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
|
||||
*/
|
||||
@Override
|
||||
public R firstValue() {
|
||||
|
||||
List<T> results = createQuery().limit(1).fetch();
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T one = results.get(0);
|
||||
return getConversionFunction().apply(one);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
|
||||
*/
|
||||
@Override
|
||||
public List<R> all() {
|
||||
|
||||
List<T> results = createQuery().fetch();
|
||||
|
||||
return mapResults(results);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Page<R> page(Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
|
||||
AbstractCollQuery<T, ?> query = createQuery();
|
||||
|
||||
if (pageable.isPaged() || pageable.getSort().isSorted()) {
|
||||
|
||||
query.offset(pageable.getOffset());
|
||||
query.limit(pageable.getPageSize());
|
||||
|
||||
if (pageable.getSort().isSorted()) {
|
||||
query.orderBy(toOrderSpecifier(pageable.getSort(), builder));
|
||||
}
|
||||
}
|
||||
QueryResults<T> results = query.limit(pageable.getPageSize()).offset(pageable.getOffset()).fetchResults();
|
||||
|
||||
return new PageImpl<>(mapResults(results.getResults()), pageable, results.getTotal());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
|
||||
*/
|
||||
@Override
|
||||
public Stream<R> stream() {
|
||||
return createQuery().stream().map(getConversionFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
|
||||
*/
|
||||
@Override
|
||||
public long count() {
|
||||
return createQuery().fetchCount();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
|
||||
*/
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return count() > 0;
|
||||
}
|
||||
|
||||
private AbstractCollQuery<T, ?> createQuery() {
|
||||
|
||||
AbstractCollQuery<T, ?> query = prepareQuery(predicate);
|
||||
|
||||
if (sort.isSorted()) {
|
||||
query.orderBy(toOrderSpecifier(sort, builder));
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<R> mapResults(List<T> results) {
|
||||
|
||||
if (entityType == resultType) {
|
||||
return (List<R>) results;
|
||||
}
|
||||
|
||||
List<R> mapped = new ArrayList<>(results.size());
|
||||
|
||||
Function<Object, R> converter = getConversionFunction();
|
||||
for (T result : results) {
|
||||
mapped.add(converter.apply(result));
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <P> Function<Object, P> getConversionFunction(Class<?> inputType, Class<P> targetType) {
|
||||
|
||||
if (targetType.isAssignableFrom(inputType)) {
|
||||
return (Function<Object, P>) Function.identity();
|
||||
}
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
return o -> projectionFactory.createProjection(targetType, o);
|
||||
}
|
||||
|
||||
DtoInstantiatingConverter converter = new DtoInstantiatingConverter(targetType, context, entityInstantiators);
|
||||
|
||||
return o -> (P) converter.convert(o);
|
||||
}
|
||||
|
||||
private Function<Object, R> getConversionFunction() {
|
||||
return getConversionFunction(entityType, resultType);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.keyvalue.core.KeyValueOperations;
|
||||
import org.springframework.data.keyvalue.repository.KeyValueRepository;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.querydsl.EntityPathResolver;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
@@ -77,7 +78,8 @@ public class QuerydslKeyValueRepository<T, ID> extends SimpleKeyValueRepository<
|
||||
|
||||
Assert.notNull(resolver, "EntityPathResolver must not be null!");
|
||||
|
||||
this.executor = new QuerydslKeyValuePredicateExecutor<>(entityInformation, operations, resolver);
|
||||
this.executor = new QuerydslKeyValuePredicateExecutor<>(entityInformation, new SpelAwareProxyProjectionFactory(),
|
||||
operations, resolver);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -152,6 +154,10 @@ public class QuerydslKeyValueRepository<T, ID> extends SimpleKeyValueRepository<
|
||||
return executor.exists(predicate);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
|
||||
*/
|
||||
@Override
|
||||
public <S extends T, R> R findBy(Predicate predicate,
|
||||
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
|
||||
Reference in New Issue
Block a user