From 201b957f9e2cd06d3fdcf4cfe1a12c1c6f436ed8 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 11 Oct 2021 10:26:28 +0200 Subject: [PATCH] Extract `QuerydslPredicateExecutor` to its own fragment. Closes #398 --- .../support/KeyValueRepositoryFactory.java | 41 +++- .../QuerydslKeyValuePredicateExecutor.java | 231 ++++++++++++++++++ .../support/QuerydslKeyValueRepository.java | 105 ++------ .../QuerydslKeyValueRepositoryUnitTests.java | 8 - 4 files changed, 284 insertions(+), 101 deletions(-) create mode 100644 src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java 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 db5302c..963b50f 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 @@ -16,12 +16,14 @@ package org.springframework.data.keyvalue.repository.support; import static org.springframework.data.querydsl.QuerydslUtils.*; +import static org.springframework.data.repository.core.support.RepositoryComposition.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Optional; import org.springframework.beans.BeanUtils; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.keyvalue.core.KeyValueOperations; import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery; import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; @@ -136,8 +138,43 @@ public class KeyValueRepositoryFactory extends RepositoryFactorySupport { */ @Override protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { - return isQueryDslRepository(metadata.getRepositoryInterface()) ? QuerydslKeyValueRepository.class - : SimpleKeyValueRepository.class; + return SimpleKeyValueRepository.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryFragments(org.springframework.data.repository.core.RepositoryMetadata) + */ + @Override + protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { + return getRepositoryFragments(metadata, keyValueOperations); + } + + /** + * Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add Key-Value-specific extensions. + * Typically adds a {@link QuerydslMongoPredicateExecutor} 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. + * @return + * @since 2.6 + */ + protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata, KeyValueOperations operations) { + + if (isQueryDslRepository(metadata.getRepositoryInterface())) { + + if (metadata.isReactiveRepository()) { + throw new InvalidDataAccessApiUsageException( + "Cannot combine Querydsl and reactive repository support in a single interface"); + } + + return RepositoryFragments + .just(new QuerydslKeyValuePredicateExecutor<>(getEntityInformation(metadata.getDomainType()), operations)); + } + + 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 new file mode 100644 index 0000000..e4f6b26 --- /dev/null +++ b/src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValuePredicateExecutor.java @@ -0,0 +1,231 @@ +/* + * 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 static org.springframework.data.keyvalue.repository.support.KeyValueQuerydslUtils.*; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Page; +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.querydsl.EntityPathResolver; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.query.FluentQuery; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import com.querydsl.collections.AbstractCollQuery; +import com.querydsl.collections.CollQuery; +import com.querydsl.core.NonUniqueResultException; +import com.querydsl.core.types.EntityPath; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.PathBuilder; + +/** + * {@link QuerydslPredicateExecutor} capable of applying {@link Predicate}s using {@link CollQuery}. + * + * @author Mark Paluch + * @since 2.6 + */ +public class QuerydslKeyValuePredicateExecutor implements QuerydslPredicateExecutor { + + private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; + + private final PathBuilder builder; + private final Supplier> findAll; + + /** + * Creates a new {@link QuerydslKeyValuePredicateExecutor} for the given {@link EntityInformation}. + * + * @param entityInformation must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + public QuerydslKeyValuePredicateExecutor(EntityInformation entityInformation, KeyValueOperations operations) { + this(entityInformation, operations, DEFAULT_ENTITY_PATH_RESOLVER); + } + + /** + * Creates a new {@link QuerydslKeyValuePredicateExecutor} for the given {@link EntityInformation}, and + * {@link EntityPathResolver}. + * + * @param entityInformation 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, + EntityPathResolver resolver) { + + Assert.notNull(resolver, "EntityPathResolver must not be null!"); + + EntityPath path = resolver.createPath(entityInformation.getJavaType()); + this.builder = new PathBuilder<>(path.getType(), path.getMetadata()); + findAll = () -> operations.findAll(entityInformation.getJavaType()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findOne(com.querydsl.core.types.Predicate) + */ + @Override + public Optional findOne(Predicate predicate) { + + Assert.notNull(predicate, "Predicate must not be null!"); + + try { + return Optional.ofNullable(prepareQuery(predicate).fetchOne()); + } catch (NonUniqueResultException o_O) { + throw new IncorrectResultSizeDataAccessException("Expected one or no result but found more than one!", 1, o_O); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate) + */ + @Override + public Iterable findAll(Predicate predicate) { + + Assert.notNull(predicate, "Predicate must not be null!"); + + return prepareQuery(predicate).fetchResults().getResults(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, com.querydsl.core.types.OrderSpecifier[]) + */ + @Override + public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { + + Assert.notNull(predicate, "Predicate must not be null!"); + Assert.notNull(orders, "OrderSpecifiers must not be null!"); + + AbstractCollQuery query = prepareQuery(predicate); + query.orderBy(orders); + + return query.fetchResults().getResults(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Predicate predicate, Sort sort) { + + Assert.notNull(predicate, "Predicate must not be null!"); + Assert.notNull(sort, "Sort must not be null!"); + + return findAll(predicate, toOrderSpecifier(sort, builder)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Predicate predicate, Pageable pageable) { + + Assert.notNull(predicate, "Predicate must not be null!"); + Assert.notNull(pageable, "Pageable must not be null!"); + + AbstractCollQuery query = prepareQuery(predicate); + + if (pageable.isPaged() || pageable.getSort().isSorted()) { + + query.offset(pageable.getOffset()); + query.limit(pageable.getPageSize()); + + if (pageable.getSort().isSorted()) { + query.orderBy(toOrderSpecifier(pageable.getSort(), builder)); + } + } + + return new PageImpl<>(query.fetchResults().getResults(), pageable, count(predicate)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[]) + */ + @Override + public Iterable findAll(OrderSpecifier... orders) { + + Assert.notNull(orders, "OrderSpecifiers must not be null!"); + + if (orders.length == 0) { + return findAll.get(); + } + + AbstractCollQuery query = prepareQuery(null); + query.orderBy(orders); + + return query.fetchResults().getResults(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.querydsl.core.types.Predicate) + */ + @Override + public long count(Predicate predicate) { + + Assert.notNull(predicate, "Predicate must not be null!"); + + return prepareQuery(predicate).fetchCount(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.querydsl.core.types.Predicate) + */ + @Override + public boolean exists(Predicate predicate) { + + Assert.notNull(predicate, "Predicate must not be null!"); + + return count(predicate) > 0; + } + + @Override + public R findBy(Predicate predicate, + Function, R> queryFunction) { + throw new UnsupportedOperationException("Not yet supported"); + } + + /** + * Creates executable query for given {@link Predicate}. + * + * @param predicate + * @return + */ + protected AbstractCollQuery prepareQuery(@Nullable Predicate predicate) { + + CollQuery query = new CollQuery<>(); + query.from(builder, findAll.get()); + + return predicate != null ? query.where(predicate) : query; + } +} 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 dd9f25e..31e4c99 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 @@ -15,14 +15,10 @@ */ package org.springframework.data.keyvalue.repository.support; -import static org.springframework.data.keyvalue.repository.support.KeyValueQuerydslUtils.*; - import java.util.Optional; import java.util.function.Function; -import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.Page; -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; @@ -32,17 +28,11 @@ import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.SimpleEntityPathResolver; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.query.FluentQuery; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import com.querydsl.collections.AbstractCollQuery; import com.querydsl.collections.CollQuery; -import com.querydsl.core.NonUniqueResultException; -import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.PathBuilder; /** * {@link KeyValueRepository} implementation capable of executing {@link Predicate}s using {@link CollQuery}. @@ -53,13 +43,13 @@ import com.querydsl.core.types.dsl.PathBuilder; * @author Mark Paluch * @param the domain type to manage * @param the identifier type of the domain type + * @deprecated since 2.6, use {@link QuerydslKeyValuePredicateExecutor} instead. */ +@Deprecated public class QuerydslKeyValueRepository extends SimpleKeyValueRepository implements QuerydslPredicateExecutor { - private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE; - - private final PathBuilder builder; + private final QuerydslKeyValuePredicateExecutor executor; /** * Creates a new {@link QuerydslKeyValueRepository} for the given {@link EntityInformation} and @@ -69,7 +59,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< * @param operations must not be {@literal null}. */ public QuerydslKeyValueRepository(EntityInformation entityInformation, KeyValueOperations operations) { - this(entityInformation, operations, DEFAULT_ENTITY_PATH_RESOLVER); + this(entityInformation, operations, SimpleEntityPathResolver.INSTANCE); } /** @@ -87,8 +77,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< Assert.notNull(resolver, "EntityPathResolver must not be null!"); - EntityPath path = resolver.createPath(entityInformation.getJavaType()); - this.builder = new PathBuilder<>(path.getType(), path.getMetadata()); + this.executor = new QuerydslKeyValuePredicateExecutor<>(entityInformation, operations, resolver); } /* @@ -97,14 +86,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Optional findOne(Predicate predicate) { - - Assert.notNull(predicate, "Predicate must not be null!"); - - try { - return Optional.ofNullable(prepareQuery(predicate).fetchOne()); - } catch (NonUniqueResultException o_O) { - throw new IncorrectResultSizeDataAccessException("Expected one or no result but found more than one!", 1, o_O); - } + return executor.findOne(predicate); } /* @@ -113,10 +95,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Iterable findAll(Predicate predicate) { - - Assert.notNull(predicate, "Predicate must not be null!"); - - return prepareQuery(predicate).fetchResults().getResults(); + return executor.findAll(predicate); } /* @@ -125,14 +104,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { - - Assert.notNull(predicate, "Predicate must not be null!"); - Assert.notNull(orders, "OrderSpecifiers must not be null!"); - - AbstractCollQuery query = prepareQuery(predicate); - query.orderBy(orders); - - return query.fetchResults().getResults(); + return executor.findAll(predicate, orders); } /* @@ -141,11 +113,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Iterable findAll(Predicate predicate, Sort sort) { - - Assert.notNull(predicate, "Predicate must not be null!"); - Assert.notNull(sort, "Sort must not be null!"); - - return findAll(predicate, toOrderSpecifier(sort, builder)); + return executor.findAll(predicate, sort); } /* @@ -154,23 +122,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Page findAll(Predicate predicate, Pageable pageable) { - - Assert.notNull(predicate, "Predicate must not be null!"); - Assert.notNull(pageable, "Pageable must not be null!"); - - AbstractCollQuery query = prepareQuery(predicate); - - if (pageable.isPaged() || pageable.getSort().isSorted()) { - - query.offset(pageable.getOffset()); - query.limit(pageable.getPageSize()); - - if (pageable.getSort().isSorted()) { - query.orderBy(toOrderSpecifier(pageable.getSort(), builder)); - } - } - - return new PageImpl<>(query.fetchResults().getResults(), pageable, count(predicate)); + return executor.findAll(predicate, pageable); } /* @@ -179,17 +131,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public Iterable findAll(OrderSpecifier... orders) { - - Assert.notNull(orders, "OrderSpecifiers must not be null!"); - - if (ObjectUtils.isEmpty(orders)) { - return findAll(); - } - - AbstractCollQuery query = prepareQuery(null); - query.orderBy(orders); - - return query.fetchResults().getResults(); + return executor.findAll(orders); } /* @@ -198,10 +140,7 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public long count(Predicate predicate) { - - Assert.notNull(predicate, "Predicate must not be null!"); - - return prepareQuery(predicate).fetchCount(); + return executor.count(predicate); } /* @@ -210,29 +149,13 @@ public class QuerydslKeyValueRepository extends SimpleKeyValueRepository< */ @Override public boolean exists(Predicate predicate) { - - Assert.notNull(predicate, "Predicate must not be null!"); - - return count(predicate) > 0; + return executor.exists(predicate); } @Override public R findBy(Predicate predicate, Function, R> queryFunction) { - throw new UnsupportedOperationException("Not yet supported"); + return executor.findBy(predicate, queryFunction); } - /** - * Creates executable query for given {@link Predicate}. - * - * @param predicate - * @return - */ - protected AbstractCollQuery prepareQuery(@Nullable Predicate predicate) { - - CollQuery query = new CollQuery<>(); - query.from(builder, findAll()); - - return predicate != null ? query.where(predicate) : query; - } } diff --git a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java b/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java index ca62db3..5976c05 100644 --- a/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/map/QuerydslKeyValueRepositoryUnitTests.java @@ -16,12 +16,10 @@ package org.springframework.data.map; import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.Assumptions.*; import java.util.List; import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -37,7 +35,6 @@ import org.springframework.data.map.QuerydslKeyValueRepositoryUnitTests.QPersonR import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.util.Streamable; -import org.springframework.data.util.Version; /** * Unit tests for {@link QuerydslKeyValueRepository}. @@ -49,11 +46,6 @@ import org.springframework.data.util.Version; */ public class QuerydslKeyValueRepositoryUnitTests extends AbstractRepositoryUnitTests { - @BeforeEach - void before() { - assumeThat(Version.javaVersion().toString()).startsWith("1.8"); - } - @Test // DATACMNS-525 void findOneIsExecutedCorrectly() {