diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/DtoInstantiatingConverter.java b/src/main/java/org/springframework/data/keyvalue/repository/support/DtoInstantiatingConverter.java new file mode 100644 index 0000000..d100ab8 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/DtoInstantiatingConverter.java @@ -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 { + + private final Class targetType; + private final MappingContext, ? 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 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> 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; + } +} diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java index b0f4c1b..25e356c 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactory.java @@ -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. *

* 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(); diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java index e4f6b26..585e51c 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java @@ -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 implements QuerydslPredicateEx private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; + private final MappingContext, ? extends PersistentProperty> context; private final PathBuilder builder; private final Supplier> findAll; + private final EntityInformation 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 implements QuerydslPredicateEx * @param operations must not be {@literal null}. */ public QuerydslKeyValuePredicateExecutor(EntityInformation 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 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 entityInformation, KeyValueOperations operations, + public QuerydslKeyValuePredicateExecutor(EntityInformation 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 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 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 R findBy(Predicate predicate, Function, 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) entityInformation.getJavaType())); } /** @@ -228,4 +261,219 @@ public class QuerydslKeyValuePredicateExecutor 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 implements FluentQuery.FetchableFluentQuery { + + private final Predicate predicate; + private final Sort sort; + private final Class entityType; + private final Class resultType; + private final List fieldsToInclude; + + FluentQuerydsl(Predicate predicate, Class resultType) { + this(predicate, Sort.unsorted(), resultType, resultType, Collections.emptyList()); + } + + public FluentQuerydsl(Predicate predicate, Sort sort, Class entityType, Class resultType, + List 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 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 FluentQuery.FetchableFluentQuery as(Class 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 project(Collection 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 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 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 all() { + + List 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 page(Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null!"); + + AbstractCollQuery 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 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 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 createQuery() { + + AbstractCollQuery query = prepareQuery(predicate); + + if (sort.isSorted()) { + query.orderBy(toOrderSpecifier(sort, builder)); + } + return query; + } + + @SuppressWarnings("unchecked") + private List mapResults(List results) { + + if (entityType == resultType) { + return (List) results; + } + + List mapped = new ArrayList<>(results.size()); + + Function converter = getConversionFunction(); + for (T result : results) { + mapped.add(converter.apply(result)); + } + + return mapped; + } + + @SuppressWarnings("unchecked") + private

Function getConversionFunction(Class inputType, Class

targetType) { + + if (targetType.isAssignableFrom(inputType)) { + return (Function) 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 getConversionFunction() { + return getConversionFunction(entityType, resultType); + } + + } } diff --git a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java index 31e4c99..54d9146 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java @@ -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 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 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 R findBy(Predicate predicate, Function, R> queryFunction) { diff --git a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java b/src/test/java/org/springframework/data/map/QuerydslKeyValuePredicateExecutorUnitTests.java similarity index 52% rename from src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java rename to src/test/java/org/springframework/data/map/QuerydslKeyValuePredicateExecutorUnitTests.java index 5976c05..4d2e2cc 100644 --- a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/map/QuerydslKeyValuePredicateExecutorUnitTests.java @@ -17,9 +17,14 @@ package org.springframework.data.map; import static org.assertj.core.api.Assertions.*; +import lombok.Data; + import java.util.List; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -30,27 +35,30 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.data.keyvalue.Person; import org.springframework.data.keyvalue.QPerson; import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory; -import org.springframework.data.keyvalue.repository.support.QuerydslKeyValueRepository; -import org.springframework.data.map.QuerydslKeyValueRepositoryUnitTests.QPersonRepository; +import org.springframework.data.map.QuerydslKeyValuePredicateExecutorUnitTests.QPersonRepository; import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.util.Streamable; /** - * Unit tests for {@link QuerydslKeyValueRepository}. + * Unit tests for {@link org.springframework.data.keyvalue.repository.support.QuerydslKeyValuePredicateExecutor}. * * @author Christoph Strobl * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch */ -public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitTests { +class QuerydslKeyValuePredicateExecutorUnitTests extends AbstractRepositoryUnitTests { + + @BeforeEach + void setUp() { + repository.saveAll(LENNISTERS); + } @Test // DATACMNS-525 void findOneIsExecutedCorrectly() { - repository.saveAll(LENNISTERS); - Optional result = repository.findOne(QPerson.person.firstname.eq(CERSEI.getFirstname())); assertThat(result).hasValue(CERSEI); } @@ -58,8 +66,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATACMNS-525 void findAllIsExecutedCorrectly() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(QPerson.person.age.eq(CERSEI.getAge())); assertThat(result).contains(CERSEI, JAIME); } @@ -67,7 +73,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATACMNS-525 void findWithPaginationWorksCorrectly() { - repository.saveAll(LENNISTERS); Page page1 = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), PageRequest.of(0, 1)); assertThat(page1.getTotalElements()).isEqualTo(2L); @@ -84,8 +89,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATACMNS-525 void findAllUsingOrderSpecifierWorksCorrectly() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), QPerson.person.firstname.desc()); @@ -95,8 +98,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATACMNS-525 void findAllUsingPageableWithSortWorksCorrectly() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), PageRequest.of(0, 10, Direction.DESC, "firstname")); @@ -106,8 +107,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATACMNS-525 void findAllUsingPagableWithQSortWorksCorrectly() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(QPerson.person.age.eq(CERSEI.getAge()), PageRequest.of(0, 10, new QSort(QPerson.person.firstname.desc()))); @@ -117,8 +116,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATAKV-90 void findAllWithOrderSpecifierWorksCorrectly() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(new QSort(QPerson.person.firstname.desc())); assertThat(result).containsExactly(TYRION, JAIME, CERSEI); @@ -132,8 +129,6 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATAKV-90, DATAKV-197 void findAllShouldAllowUnsortedFindAll() { - repository.saveAll(LENNISTERS); - Iterable result = repository.findAll(Sort.unsorted()); assertThat(result).contains(TYRION, JAIME, CERSEI); @@ -141,17 +136,12 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATAKV-95 void executesExistsCorrectly() { - - repository.saveAll(LENNISTERS); - assertThat(repository.exists(QPerson.person.age.eq(CERSEI.getAge()))).isTrue(); } @Test // DATAKV-96 void shouldSupportFindAllWithPredicateAndSort() { - repository.saveAll(LENNISTERS); - List users = Streamable.of(repository.findAll(person.age.gt(0), Sort.by(Direction.ASC, "firstname"))) .toList(); @@ -164,12 +154,148 @@ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitT @Test // DATAKV-179 void throwsExceptionIfMoreThanOneResultIsFound() { - repository.saveAll(LENNISTERS); - assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class) // .isThrownBy(() -> repository.findOne(person.firstname.contains("e"))); } + @Test // GH-397 + void findByShouldReturnFirst() { + + Person first = repository.findBy(QPerson.person.firstname.eq("tyrion"), + FluentQuery.FetchableFluentQuery::firstValue); + + assertThat(first).isEqualTo(TYRION); + + first = repository.findBy(QPerson.person.firstname.eq("foo"), Function.identity()).firstValue(); + + assertThat(first).isNull(); + } + + @Test // GH-397 + void findByShouldReturnOne() { + + assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class) + .isThrownBy(() -> repository.findBy(QPerson.person.firstname.ne("foo"), FluentQuery.FetchableFluentQuery::one)); + + Person one = repository.findBy(QPerson.person.firstname.eq("tyrion"), FluentQuery.FetchableFluentQuery::oneValue); + + assertThat(one).isEqualTo(TYRION); + } + + @Test // GH-397 + void findByShouldReturnFirstWithProjection() { + + PersonProjection interfaceProjection = repository.findBy(QPerson.person.firstname.eq("tyrion"), + it -> it.as(PersonProjection.class).firstValue()); + assertThat(interfaceProjection.getFirstname()).isEqualTo("tyrion"); + + PersonDto dto = repository.findBy(QPerson.person.firstname.eq("tyrion"), it -> it.as(PersonDto.class).firstValue()); + assertThat(dto.getFirstname()).isEqualTo("tyrion"); + } + + @Test // GH-397 + void findByShouldReturnOneWithProjection() { + + PersonProjection interfaceProjection = repository.findBy(QPerson.person.firstname.eq("tyrion"), + it -> it.as(PersonProjection.class).oneValue()); + assertThat(interfaceProjection.getFirstname()).isEqualTo("tyrion"); + + PersonDto dto = repository.findBy(QPerson.person.firstname.eq("tyrion"), it -> it.as(PersonDto.class).oneValue()); + assertThat(dto.getFirstname()).isEqualTo("tyrion"); + } + + @Test // GH-397 + void findByShouldReturnAll() { + + List all = repository.findBy(QPerson.person.firstname.eq("tyrion"), FluentQuery.FetchableFluentQuery::all); + + assertThat(all).contains(TYRION); + } + + @Test // GH-397 + void findByShouldReturnAllSorted() { + + List all = repository.findBy(QPerson.person.firstname.ne("foo"), + q -> q.sortBy(Sort.by(Direction.ASC, "firstname")).all()); + + assertThat(all).containsSequence(CERSEI, JAIME, TYRION); + + all = repository.findBy(QPerson.person.firstname.ne("foo"), + q -> q.sortBy(Sort.by(Direction.DESC, "firstname")).all()); + + assertThat(all).containsSequence(TYRION, JAIME, CERSEI); + } + + @Test // GH-397 + void findByShouldReturnAllWithProjection() { + + Stream all = repository.findBy(QPerson.person.firstname.eq("tyrion"), + q -> q.as(PersonProjection.class).stream()); + + assertThat(all).hasOnlyElementsOfType(PersonProjection.class); + } + + @Test // GH-397 + void findByShouldReturnPage() { + + Page page = repository.findBy(QPerson.person.firstname.ne("foo"), + it -> it.as(PersonProjection.class).page(PageRequest.of(0, 1, Sort.by("firstname")))); + + assertThat(page.getContent().get(0).getFirstname()).isEqualTo("cersei"); + assertThat(page.getTotalPages()).isEqualTo(3); + + Page nextPage = repository.findBy(QPerson.person.firstname.ne("foo"), + it -> it.as(PersonProjection.class).page(page.nextPageable())); + + assertThat(nextPage.getContent().get(0).getFirstname()).isEqualTo("jaime"); + assertThat(nextPage.getTotalPages()).isEqualTo(3); + } + + @Test // GH-397 + void findByShouldReturnStream() { + + List all = repository.findBy(QPerson.person.firstname.eq("tyrion"), FluentQuery.FetchableFluentQuery::all); + + assertThat(all).contains(TYRION); + } + + @Test // GH-397 + void findByShouldReturnStreamWithProjection() { + + Stream all = repository.findBy(QPerson.person.firstname.eq("tyrion"), + q -> q.as(PersonProjection.class).stream()); + + assertThat(all).hasOnlyElementsOfType(PersonProjection.class); + } + + @Test // GH-397 + void findByShouldReturnCount() { + + long count = repository.findBy(QPerson.person.firstname.ne("foo"), FluentQuery.FetchableFluentQuery::count); + + assertThat(count).isEqualTo(3); + } + + @Test // GH-397 + void findByShouldReturnExists() { + + boolean exists = repository.findBy(QPerson.person.firstname.eq("tyrion"), FluentQuery.FetchableFluentQuery::exists); + assertThat(exists).isTrue(); + + exists = repository.findBy(QPerson.person.firstname.eq("foo"), FluentQuery.FetchableFluentQuery::exists); + assertThat(exists).isFalse(); + } + + interface PersonProjection { + String getFirstname(); + } + + @Data + static class PersonDto { + + String firstname; + } + /* * (non-Javadoc) * @see org.springframework.data.map.SimpleKeyValueRepositoryUnitTests#getRepository(org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory)