From 4b7f9e91c52ecf1d4b46b5d109e77050bbb0bd39 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 2 Jul 2020 16:07:18 +0200 Subject: [PATCH] DATAJPA-872 - Allow for duplicate hints. Some JPA implementations allow to have multiple hints for the same key/name. The former implementation did not support that since it stored hints as Maps. With this change they are now stored as lists and duplicates are passed on to the JPA implementation. How the implementation deals with such a situation depends on that implementation. Original pull request: #426. --- .../repository/query/AbstractJpaQuery.java | 8 +-- .../data/jpa/repository/query/Jpa21Utils.java | 23 +++---- .../support/CrudMethodMetadata.java | 22 +++--- .../CrudMethodMetadataPostProcessor.java | 36 ++++------ .../repository/support/DefaultQueryHints.java | 37 +++------- .../repository/support/QueryHintValue.java | 61 ++++++++++++++++ .../jpa/repository/support/QueryHints.java | 42 ++++------- .../support/QuerydslJpaPredicateExecutor.java | 5 +- .../support/QuerydslJpaRepository.java | 6 +- .../support/SimpleJpaRepository.java | 10 ++- .../repository/support/SimpleQueryHints.java | 54 +++++++++++++++ .../support/DefaultQueryHintsTest.java | 40 +++++++---- .../support/SimpleJpaRepositoryUnitTests.java | 4 ++ .../support/SimpleQueryHintsUnitTests.java | 69 +++++++++++++++++++ 14 files changed, 281 insertions(+), 136 deletions(-) create mode 100644 src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java create mode 100644 src/main/java/org/springframework/data/jpa/repository/support/SimpleQueryHints.java create mode 100644 src/test/java/org/springframework/data/jpa/repository/support/SimpleQueryHintsUnitTests.java diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 556e866b8..541f9dd0b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -41,6 +41,8 @@ import org.springframework.data.jpa.repository.query.JpaQueryExecution.Procedure import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution; +import org.springframework.data.jpa.repository.support.QueryHintValue; +import org.springframework.data.jpa.repository.support.SimpleQueryHints; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; @@ -239,12 +241,10 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { JpaEntityGraph entityGraph = method.getEntityGraph(); if (entityGraph != null) { - Map hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), + SimpleQueryHints hints = Jpa21Utils.getFetchGraphHint(em, method.getEntityGraph(), getQueryMethod().getEntityInformation().getJavaType()); - for (Map.Entry hint : hints.entrySet()) { - query.setHint(hint.getKey(), hint.getValue()); - } + hints.forEach(query::setHint); } return query; diff --git a/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java index 436cf569a..850d916c2 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java @@ -27,6 +27,8 @@ import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.Subgraph; +import org.springframework.data.jpa.repository.support.QueryHintValue; +import org.springframework.data.jpa.repository.support.SimpleQueryHints; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -41,6 +43,7 @@ import org.springframework.util.StringUtils; * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Jens Schauder * @since 1.6 */ public class Jpa21Utils { @@ -62,29 +65,23 @@ public class Jpa21Utils { // prevent instantiation } - /** - * Returns a {@link Map} with hints for a JPA 2.1 fetch-graph or load-graph if running under JPA 2.1. - * - * @param em must not be {@literal null}. - * @param entityGraph can be {@literal null}. - * @param entityType must not be {@literal null}. - * @return a {@code Map} with the hints or an empty {@code Map} if no hints were found. - * @since 1.8 - */ - public static Map tryGetFetchGraphHints(EntityManager em, @Nullable JpaEntityGraph entityGraph, + public static SimpleQueryHints getFetchGraphHint(EntityManager em, @Nullable JpaEntityGraph entityGraph, Class entityType) { + SimpleQueryHints result = new SimpleQueryHints(); + if (entityGraph == null) { - return Collections.emptyMap(); + return result; } EntityGraph graph = tryGetFetchGraph(em, entityGraph, entityType); if (graph == null) { - return Collections.emptyMap(); + return result; } - return Collections. singletonMap(entityGraph.getType().getKey(), graph); + result.add(entityGraph.getType().getKey(), graph); + return result; } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index 57630317e..018104fb3 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -16,8 +16,12 @@ package org.springframework.data.jpa.repository.support; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; import javax.persistence.LockModeType; @@ -45,22 +49,20 @@ public interface CrudMethodMetadata { LockModeType getLockModeType(); /** - * Returns all query hints to be applied to queries executed for the CRUD method. - * + * Returns all query hints in a list to be applied to queries executed for the CRUD method. + * * @return + * @since 2.4 */ - Map getQueryHints(); + SimpleQueryHints getQueryHints(); /** - * Returns all query hints to be applied to count queries executed for the CRUD method. The default implementation - * just delegates to {@link #getQueryHints()}. - * + * Returns all query hints in a list to be applied to queries executed for the CRUD method. + * * @return - * @since 2.2 + * @since 2.4 */ - default Map getQueryHintsForCount() { - return getQueryHints(); - } + SimpleQueryHints getQueryHintsForCount(); /** * Returns the {@link EntityGraph} to be used. diff --git a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 8b2bd3bca..0973e0eb0 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -16,10 +16,8 @@ package org.springframework.data.jpa.repository.support; import java.lang.reflect.Method; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -31,7 +29,6 @@ import javax.persistence.QueryHint; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; - import org.springframework.aop.TargetSource; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; @@ -194,8 +191,8 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { private final @Nullable LockModeType lockModeType; - private final Map queryHints; - private final Map getQueryHintsForCount; + private final SimpleQueryHints queryHints; + private final SimpleQueryHints queryHintsForCount; private final Optional entityGraph; private final Method method; @@ -210,7 +207,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B this.lockModeType = findLockModeType(method); this.queryHints = findQueryHints(method, it -> true); - this.getQueryHintsForCount = findQueryHints(method, QueryHints::forCounting); + this.queryHintsForCount = findQueryHints(method, QueryHints::forCounting); this.entityGraph = findEntityGraph(method); this.method = method; } @@ -226,25 +223,26 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation); } - private static Map findQueryHints(Method method, Predicate annotationFilter) { + private static SimpleQueryHints findQueryHints(Method method, Predicate annotationFilter) { + + SimpleQueryHints queryHints = new SimpleQueryHints(); - Map queryHints = new HashMap<>(); QueryHints queryHintsAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class); if (queryHintsAnnotation != null && annotationFilter.test(queryHintsAnnotation)) { for (QueryHint hint : queryHintsAnnotation.value()) { - queryHints.put(hint.name(), hint.value()); + queryHints.add(hint.name(), hint.value()); } } QueryHint queryHintAnnotation = AnnotationUtils.findAnnotation(method, QueryHint.class); if (queryHintAnnotation != null) { - queryHints.put(queryHintAnnotation.name(), queryHintAnnotation.value()); + queryHints.add(queryHintAnnotation.name(), queryHintAnnotation.value()); } - return Collections.unmodifiableMap(queryHints); + return queryHints; } /* @@ -257,22 +255,14 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B return lockModeType; } - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getQueryHints() - */ @Override - public Map getQueryHints() { + public SimpleQueryHints getQueryHints() { return queryHints; } - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getQueryHintsForCount() - */ @Override - public Map getQueryHintsForCount() { - return getQueryHintsForCount; + public SimpleQueryHints getQueryHintsForCount() { + return queryHintsForCount; } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java b/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java index 6f35c8e0a..904b6f069 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java @@ -15,12 +15,8 @@ */ package org.springframework.data.jpa.repository.support; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; +import java.util.function.BiConsumer; import javax.persistence.EntityManager; @@ -97,41 +93,26 @@ class DefaultQueryHints implements QueryHints { return new DefaultQueryHints(this.information, this.metadata, this.entityManager, true); } - /* - * (non-Javadoc) - * @see java.lang.Iterable#iterator() - */ @Override - public Iterator> iterator() { - return asMap().entrySet().iterator(); + public void forEach(BiConsumer consumer) { + combineHints().forEach(consumer); } - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.QueryHints#asMap() - */ - @Override - public Map asMap() { + private SimpleQueryHints combineHints() { - Map hints = new HashMap<>(); + SimpleQueryHints hints = forCounts ? metadata.getQueryHintsForCount() : metadata.getQueryHints(); - if (forCounts) { - hints.putAll(metadata.getQueryHintsForCount()); - } else { - hints.putAll(metadata.getQueryHints()); - } - - hints.putAll(getFetchGraphs()); + hints.addAll(getFetchGraphs()); return hints; } - private Map getFetchGraphs() { + private SimpleQueryHints getFetchGraphs() { return Optionals .mapIfAllPresent(entityManager, metadata.getEntityGraph(), - (em, graph) -> Jpa21Utils.tryGetFetchGraphHints(em, getEntityGraph(graph), information.getJavaType())) - .orElse(Collections.emptyMap()); + (em, graph) -> Jpa21Utils.getFetchGraphHint(em, getEntityGraph(graph), information.getJavaType())) + .orElse(new SimpleQueryHints()); } private JpaEntityGraph getEntityGraph(EntityGraph entityGraph) { diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java new file mode 100644 index 000000000..34edc2a26 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryHintValue.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 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.jpa.repository.support; + +import java.util.Objects; + +import org.springframework.util.Assert; + +/** + * Value object carrying a query hint consisting of a name/key and a value. + * + * @author Jens Schauder + * @since 2.4 + */ +public class QueryHintValue { + + public final String name; + public final Object value; + + public QueryHintValue(String name, Object value) { + + Assert.notNull(name, "Name must not be null."); + Assert.notNull(value, "Value must not be null."); + + this.name = name; + this.value = value; + } + + @Override + public String toString() { + return "QueryHintValue{" + "name='" + name + '\'' + ", value='" + value + '\'' + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + QueryHintValue that = (QueryHintValue) o; + return name.equals(that.name) && value.equals(that.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java b/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java index e68794d38..b147a92e0 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/QueryHints.java @@ -15,23 +15,21 @@ */ package org.springframework.data.jpa.repository.support; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; +import java.util.function.BiConsumer; import javax.persistence.EntityManager; /** - * QueryHints provides access to query hints defined via {@link CrudMethodMetadata#getQueryHints()} by default excluding - * JPA {@link javax.persistence.EntityGraph}. + * QueryHints provides access to query hints defined via {@link CrudMethodMetadata#getQueryHints()} QueryHintList()} by + * default excluding JPA {@link javax.persistence.EntityGraph}. The object allows to switch between query hints for + * count queries with or without fetch graph hints. * * @author Christoph Strobl * @author Oliver Gierke * @author Jens Schauder * @since 2.0 */ -interface QueryHints extends Iterable> { +interface QueryHints { /** * Creates and returns a new {@link QueryHints} instance including {@link javax.persistence.EntityGraph}. @@ -51,11 +49,12 @@ interface QueryHints extends Iterable> { QueryHints forCounts(); /** - * Get the query hints as a {@link Map}. + * Passes each query hint to the consumer. Query hint keys might appear more than once. * - * @return never {@literal null}. + * @param consumer to process query hints consisting of a key and a value. + * @since 2.4 */ - Map asMap(); + void forEach(BiConsumer consumer); /** * Null object implementation of {@link QueryHints}. @@ -63,28 +62,10 @@ interface QueryHints extends Iterable> { * @author Oliver Gierke * @since 2.0 */ - static enum NoHints implements QueryHints { + enum NoHints implements QueryHints { INSTANCE; - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.QueryHints#asMap() - */ - @Override - public Map asMap() { - return Collections.emptyMap(); - } - - /* - * (non-Javadoc) - * @see java.lang.Iterable#iterator() - */ - @Override - public Iterator> iterator() { - return Collections.emptyIterator(); - } - /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.support.QueryHints#withFetchGraphs(javax.persistence.EntityManager) @@ -102,5 +83,8 @@ interface QueryHints extends Iterable> { public QueryHints forCounts() { return this; } + + @Override + public void forEach(BiConsumer consumer) {} } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java index 65233da04..6963328dc 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.repository.support; import java.util.List; -import java.util.Map.Entry; import java.util.Optional; import javax.persistence.EntityManager; @@ -245,9 +244,7 @@ public class QuerydslJpaPredicateExecutor implements QuerydslPredicateExecuto query = query.where(predicate); } - for (Entry hint : hints) { - query.setHint(hint.getKey(), hint.getValue()); - } + hints.forEach(query::setHint); return query; } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java index 1ab72ce0b..7817f68b0 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaRepository.java @@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.support; import java.io.Serializable; import java.util.List; -import java.util.Map.Entry; import java.util.Optional; import javax.persistence.EntityManager; @@ -48,7 +47,6 @@ import com.querydsl.jpa.impl.AbstractJPAQuery; * {@link QuerydslPredicateExecutor}. * * @deprecated Instead of this class use {@link QuerydslJpaPredicateExecutor} - * * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch @@ -222,9 +220,7 @@ public class QuerydslJpaRepository extends SimpleJpa query = query.where(predicate); } - for (Entry hint : hints) { - query.setHint(hint.getKey(), hint.getValue()); - } + hints.forEach(query::setHint); return query; } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 3169abe10..f9181959b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -20,9 +20,9 @@ import static org.springframework.data.jpa.repository.query.QueryUtils.*; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import javax.persistence.EntityManager; @@ -276,7 +276,8 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation hints = getQueryHints().withFetchGraphs(em).asMap(); + Map hints = new HashMap<>(); + getQueryHints().withFetchGraphs(em).forEach(hints::put); return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints)); } @@ -785,10 +786,7 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation hint : getQueryHints().withFetchGraphs(em)) { - query.setHint(hint.getKey(), hint.getValue()); - } + getQueryHints().withFetchGraphs(em).forEach(query::setHint); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleQueryHints.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleQueryHints.java new file mode 100644 index 000000000..10aff1f1e --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleQueryHints.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 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.jpa.repository.support; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Just the values of QueryHints, without the Option to switch between count/ fetchGraph hints. + * + * @author Jens Schauder + * @since 2.4 + * @see QueryHints + */ +public class SimpleQueryHints { + + MultiValueMap values = new LinkedMultiValueMap<>(); + + + public void add(String name, Object value) { + values.add(name, value); + } + + public void forEach(BiConsumer consumer) { + + for (Map.Entry> entry : values.entrySet()) { + + for (Object value : entry.getValue()) { + consumer.accept(entry.getKey(), value); + } + } + } + + public void addAll(SimpleQueryHints hints) { + hints.forEach(this::add); + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java b/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java index a98f71ea4..98f7b1326 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/DefaultQueryHintsTest.java @@ -18,7 +18,9 @@ package org.springframework.data.jpa.repository.support; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Before; @@ -46,9 +48,14 @@ public class DefaultQueryHintsTest { QueryHints hints = DefaultQueryHints.of(information, metadata); - assertThat(hints.asMap()) // - .extracting("name1", "name2", "n1", "n2") // - .containsExactly("value1", "value2", null, null); + Map collectedHints=new HashMap<>(); + hints.forEach(collectedHints::put); + + assertThat(collectedHints) // + .contains( // + entry("name1", "value1"), // + entry("name2", "value2") // + ); } @Test // DATAJPA-1156 @@ -56,26 +63,31 @@ public class DefaultQueryHintsTest { QueryHints hints = DefaultQueryHints.of(information, metadata).forCounts(); - assertThat(hints.asMap()) // - .extracting("name1", "name2", "n1", "n2") // - .containsExactly(null, null, "1", "2"); + Map collectedHints=new HashMap<>(); + hints.forEach(collectedHints::put); + + assertThat(collectedHints) // + .contains( // + entry("n1", "1"), // + entry("n2", "2") // + ); } private void setupMainHints() { - Map mainHintMap = new HashMap<>(); - mainHintMap.put("name1", "value1"); - mainHintMap.put("name2", "value2"); + SimpleQueryHints mainHints = new SimpleQueryHints(); + mainHints.add("name1", "value1"); + mainHints.add("name2", "value2"); - when(metadata.getQueryHints()).thenReturn(mainHintMap); + when(metadata.getQueryHints()).thenReturn(mainHints); } private void setUpCountHints() { - Map countHintMap = new HashMap<>(); - countHintMap.put("n1", "1"); - countHintMap.put("n2", "2"); + SimpleQueryHints countHints = new SimpleQueryHints(); + countHints.add("n1", "1"); + countHints.add("n2", "2"); - when(metadata.getQueryHintsForCount()).thenReturn(countHintMap); + when(metadata.getQueryHintsForCount()).thenReturn(countHints); } } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index 13a037e07..cc649efd6 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -76,6 +76,10 @@ public class SimpleJpaRepositoryUnitTests { when(em.createQuery(criteriaQuery)).thenReturn(query); when(em.createQuery(countCriteriaQuery)).thenReturn(countQuery); + SimpleQueryHints hints = new SimpleQueryHints(); + when(metadata.getQueryHints()).thenReturn(hints); + when(metadata.getQueryHintsForCount()).thenReturn(hints); + repo = new SimpleJpaRepository(information, em); repo.setRepositoryMethodMetadata(metadata); } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SimpleQueryHintsUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SimpleQueryHintsUnitTests.java new file mode 100644 index 000000000..99d887d95 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/SimpleQueryHintsUnitTests.java @@ -0,0 +1,69 @@ +package org.springframework.data.jpa.repository.support;/* + * Copyright 2020 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. + */ + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.data.util.Pair; + +public class SimpleQueryHintsUnitTests { + + @Test + public void emptyQueryHint() { + new SimpleQueryHints().forEach((k, v) -> Assertions.fail("Empty SimpleQueryHints shouldn't contain a value")); + } + + @Test + public void queryHint() { + + SimpleQueryHints hints = new SimpleQueryHints(); + hints.add("key", "value"); + hints.add("key", "other value"); + hints.add("other key", "another value"); + + List calls = new ArrayList(); + hints.forEach((k, v) -> calls.add(Pair.of(k, v))); + + assertThat(calls).containsExactlyInAnyOrder(Pair.of("key", "value"), Pair.of("key", "other value"), + Pair.of("other key", "another value")); + } + + @Test + public void addingQueryHints() { + + SimpleQueryHints hints = new SimpleQueryHints(); + hints.add("key", "value"); + hints.add("key", "other value"); + hints.add("other key", "another value"); + + SimpleQueryHints additionalHints = new SimpleQueryHints(); + additionalHints.add("key", "23"); + additionalHints.add("another key", "42"); + + hints.addAll(additionalHints); + + List calls = new ArrayList(); + hints.forEach((k, v) -> calls.add(Pair.of(k, v))); + + assertThat(calls).containsExactlyInAnyOrder(Pair.of("key", "value"), Pair.of("key", "other value"),Pair.of("key", "23"), + Pair.of("other key", "another value"), Pair.of("another key", "42")); + } + +}