DATAJPA-872 - Polishing.

Use QueryHints interface where possible and MutableQueryHints only inside of method implementations to hide mutability. Introduce QueryHints.from(…) as factory method to obtain a merged view of QueryHints.

Reorganize imports, reformat code, add Javadoc. Fix license header in test. Fix generics.

Original pull request: #426.
This commit is contained in:
Mark Paluch
2020-07-23 14:01:03 +02:00
parent 4b7f9e91c5
commit d35ee1a82b
12 changed files with 235 additions and 167 deletions

View File

@@ -41,8 +41,7 @@ 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.repository.support.QueryHints;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
@@ -241,7 +240,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
JpaEntityGraph entityGraph = method.getEntityGraph();
if (entityGraph != null) {
SimpleQueryHints hints = Jpa21Utils.getFetchGraphHint(em, method.getEntityGraph(),
QueryHints hints = Jpa21Utils.getFetchGraphHint(em, method.getEntityGraph(),
getQueryMethod().getEntityInformation().getJavaType());
hints.forEach(query::setHint);

View File

@@ -19,7 +19,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.persistence.AttributeNode;
import javax.persistence.EntityGraph;
@@ -27,8 +26,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.data.jpa.repository.support.MutableQueryHints;
import org.springframework.data.jpa.repository.support.QueryHints;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -65,10 +64,10 @@ public class Jpa21Utils {
// prevent instantiation
}
public static SimpleQueryHints getFetchGraphHint(EntityManager em, @Nullable JpaEntityGraph entityGraph,
public static QueryHints getFetchGraphHint(EntityManager em, @Nullable JpaEntityGraph entityGraph,
Class<?> entityType) {
SimpleQueryHints result = new SimpleQueryHints();
MutableQueryHints result = new MutableQueryHints();
if (entityGraph == null) {
return result;

View File

@@ -16,12 +16,7 @@
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;
@@ -50,19 +45,19 @@ public interface CrudMethodMetadata {
/**
* Returns all query hints in a list to be applied to queries executed for the CRUD method.
*
*
* @return
* @since 2.4
*/
SimpleQueryHints getQueryHints();
QueryHints getQueryHints();
/**
* Returns all query hints in a list to be applied to queries executed for the CRUD method.
*
*
* @return
* @since 2.4
*/
SimpleQueryHints getQueryHintsForCount();
QueryHints getQueryHintsForCount();
/**
* Returns the {@link EntityGraph} to be used.

View File

@@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.support;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -191,8 +190,8 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
private static class DefaultCrudMethodMetadata implements CrudMethodMetadata {
private final @Nullable LockModeType lockModeType;
private final SimpleQueryHints queryHints;
private final SimpleQueryHints queryHintsForCount;
private final org.springframework.data.jpa.repository.support.QueryHints queryHints;
private final org.springframework.data.jpa.repository.support.QueryHints queryHintsForCount;
private final Optional<EntityGraph> entityGraph;
private final Method method;
@@ -223,9 +222,10 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation);
}
private static SimpleQueryHints findQueryHints(Method method, Predicate<QueryHints> annotationFilter) {
private static org.springframework.data.jpa.repository.support.QueryHints findQueryHints(Method method,
Predicate<QueryHints> annotationFilter) {
SimpleQueryHints queryHints = new SimpleQueryHints();
MutableQueryHints queryHints = new MutableQueryHints();
QueryHints queryHintsAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class);
@@ -255,13 +255,21 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
return lockModeType;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getQueryHints()
*/
@Override
public SimpleQueryHints getQueryHints() {
public org.springframework.data.jpa.repository.support.QueryHints getQueryHints() {
return queryHints;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getQueryHintsForCount()
*/
@Override
public SimpleQueryHints getQueryHintsForCount() {
public org.springframework.data.jpa.repository.support.QueryHints getQueryHintsForCount() {
return queryHintsForCount;
}

View File

@@ -93,26 +93,25 @@ class DefaultQueryHints implements QueryHints {
return new DefaultQueryHints(this.information, this.metadata, this.entityManager, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.QueryHints#forEach(java.util.function.BiConsumer)
*/
@Override
public void forEach(BiConsumer<String, Object> consumer) {
combineHints().forEach(consumer);
public void forEach(BiConsumer<String, Object> action) {
combineHints().forEach(action);
}
private SimpleQueryHints combineHints() {
SimpleQueryHints hints = forCounts ? metadata.getQueryHintsForCount() : metadata.getQueryHints();
hints.addAll(getFetchGraphs());
return hints;
private QueryHints combineHints() {
return QueryHints.from(forCounts ? metadata.getQueryHintsForCount() : metadata.getQueryHints(), getFetchGraphs());
}
private SimpleQueryHints getFetchGraphs() {
private QueryHints getFetchGraphs() {
return Optionals
.mapIfAllPresent(entityManager, metadata.getEntityGraph(),
(em, graph) -> Jpa21Utils.getFetchGraphHint(em, getEntityGraph(graph), information.getJavaType()))
.orElse(new SimpleQueryHints());
.orElse(new MutableQueryHints());
}
private JpaEntityGraph getEntityGraph(EntityGraph entityGraph) {

View File

@@ -0,0 +1,86 @@
/*
* 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.List;
import java.util.Map;
import java.util.function.BiConsumer;
import javax.persistence.EntityManager;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Mutable implementation of {@link QueryHints}, without the Option to switch between
* {@link #forCounts()}/{@link #withFetchGraphs(EntityManager)} hints.
*
* @author Jens Schauder
* @author Mark Paluch
* @since 2.4
* @see QueryHints
*/
public class MutableQueryHints implements QueryHints {
private final MultiValueMap<String, Object> values = new LinkedMultiValueMap<>();
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.QueryHints#withFetchGraphs(javax.persistence.EntityManager)
*/
@Override
public QueryHints withFetchGraphs(EntityManager em) {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.QueryHints#forCounts()
*/
@Override
public QueryHints forCounts() {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.QueryHints#forEach(java.util.function.BiConsumer)
*/
@Override
public void forEach(BiConsumer<String, Object> action) {
for (Map.Entry<String, List<Object>> entry : values.entrySet()) {
for (Object value : entry.getValue()) {
action.accept(entry.getKey(), value);
}
}
}
/**
* Add a new key-value pair for a hint.
*
* @param name
* @param value
*/
public void add(String name, Object value) {
values.add(name, value);
}
MultiValueMap<String, Object> getValues() {
return values;
}
}

View File

@@ -19,6 +19,8 @@ import java.util.function.BiConsumer;
import javax.persistence.EntityManager;
import org.springframework.util.Assert;
/**
* 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
@@ -29,7 +31,27 @@ import javax.persistence.EntityManager;
* @author Jens Schauder
* @since 2.0
*/
interface QueryHints {
public interface QueryHints {
/**
* Create a new {@link QueryHints} object from the given {@code sources}.
*
* @param sources must not be {@literal null}.
* @return a merged representation of {@link QueryHints QueryHints}.
* @since 2.4
*/
static QueryHints from(QueryHints... sources) {
Assert.notNull(sources, "Sources must not be null!");
MutableQueryHints result = new MutableQueryHints();
for (QueryHints queryHints : sources) {
queryHints.forEach(result.getValues()::add);
}
return result;
}
/**
* Creates and returns a new {@link QueryHints} instance including {@link javax.persistence.EntityGraph}.
@@ -49,12 +71,16 @@ interface QueryHints {
QueryHints forCounts();
/**
* Performs the given action for each element of this query hints object until all hints have been processed or the
* action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions
* thrown by the action are relayed to the caller.
* <p>
* Passes each query hint to the consumer. Query hint keys might appear more than once.
*
* @param consumer to process query hints consisting of a key and a value.
* @param action to process query hints consisting of a key and a value.
* @since 2.4
*/
void forEach(BiConsumer<String, Object> consumer);
void forEach(BiConsumer<String, Object> action);
/**
* Null object implementation of {@link QueryHints}.
@@ -84,7 +110,11 @@ interface QueryHints {
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.QueryHints#forEach(java.util.function.BiConsumer)
*/
@Override
public void forEach(BiConsumer<String, Object> consumer) {}
public void forEach(BiConsumer<String, Object> action) {}
}
}

View File

@@ -1,54 +0,0 @@
/*
* 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<String, Object> values = new LinkedMultiValueMap<>();
public void add(String name, Object value) {
values.add(name, value);
}
public void forEach(BiConsumer<String, Object> consumer) {
for (Map.Entry<String, List<Object>> entry : values.entrySet()) {
for (Object value : entry.getValue()) {
consumer.accept(entry.getKey(), value);
}
}
}
public void addAll(SimpleQueryHints hints) {
hints.forEach(this::add);
}
}

View File

@@ -18,9 +18,7 @@ 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;
@@ -75,7 +73,7 @@ public class DefaultQueryHintsTest {
private void setupMainHints() {
SimpleQueryHints mainHints = new SimpleQueryHints();
MutableQueryHints mainHints = new MutableQueryHints();
mainHints.add("name1", "value1");
mainHints.add("name2", "value2");
@@ -84,7 +82,7 @@ public class DefaultQueryHintsTest {
private void setUpCountHints() {
SimpleQueryHints countHints = new SimpleQueryHints();
MutableQueryHints countHints = new MutableQueryHints();
countHints.add("n1", "1");
countHints.add("n2", "2");

View File

@@ -0,0 +1,76 @@
/*
* 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 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;
/**
* Unit tests for {@link MutableQueryHints}.
*
* @author Jens Schauder
* @author Mark Paluch
*/
public class MutableQueryHintsUnitTests {
@Test // DATAJPA-872
public void emptyQueryHint() {
new MutableQueryHints().forEach((k, v) -> Assertions.fail("Empty SimpleQueryHints shouldn't contain a value"));
}
@Test // DATAJPA-872
public void queryHint() {
MutableQueryHints hints = new MutableQueryHints();
hints.add("key", "value");
hints.add("key", "other value");
hints.add("other key", "another value");
List<Object> 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 // DATAJPA-872
public void shouldMergeQueryHints() {
MutableQueryHints hints = new MutableQueryHints();
hints.add("key", "value");
hints.add("key", "other value");
hints.add("other key", "another value");
MutableQueryHints additionalHints = new MutableQueryHints();
additionalHints.add("key", "23");
additionalHints.add("another key", "42");
QueryHints merged = QueryHints.from(hints, additionalHints);
List<Object> calls = new ArrayList<>();
merged.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"));
}
}

View File

@@ -32,6 +32,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.sample.User;
@@ -76,7 +77,7 @@ public class SimpleJpaRepositoryUnitTests {
when(em.createQuery(criteriaQuery)).thenReturn(query);
when(em.createQuery(countCriteriaQuery)).thenReturn(countQuery);
SimpleQueryHints hints = new SimpleQueryHints();
MutableQueryHints hints = new MutableQueryHints();
when(metadata.getQueryHints()).thenReturn(hints);
when(metadata.getQueryHintsForCount()).thenReturn(hints);

View File

@@ -1,69 +0,0 @@
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"));
}
}