From 6c0c5e509e5f0107ab8998676d782fe7fa0883ca Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Tue, 21 May 2019 12:26:12 +0200 Subject: [PATCH] Add support for named queries. --- .../query/Neo4jQueryLookupStrategy.java | 13 +++++++----- .../query/StringBasedNeo4jQuery.java | 21 +++++++++++++++++++ .../neo4j/integration/PersonRepository.java | 3 +++ .../data/neo4j/integration/RepositoryIT.java | 7 +++++++ .../repository/query/RepositoryQueryTest.java | 18 ++++++++++++++++ .../META-INF/neo4j-named-queries.properties | 19 +++++++++++++++++ 6 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 spring-data-neo4j/src/test/resources/META-INF/neo4j-named-queries.properties diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java index 079ced55d..1f44ab431 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQueryLookupStrategy.java @@ -55,12 +55,15 @@ public final class Neo4jQueryLookupStrategy implements QueryLookupStrategy { NamedQueries namedQueries) { Neo4jQueryMethod queryMethod = new Neo4jQueryMethod(method, metadata, factory); + String namedQueryName = queryMethod.getNamedQueryName(); - if (queryMethod.hasQueryAnnotation()) { - return StringBasedNeo4jQuery - .create(nodeManager, mappingContext, evaluationContextProvider, queryMethod); + if (namedQueries.hasQuery(namedQueryName)) { + return StringBasedNeo4jQuery.create(nodeManager, mappingContext, evaluationContextProvider, queryMethod, + namedQueries.getQuery(namedQueryName)); + } else if (queryMethod.hasQueryAnnotation()) { + return StringBasedNeo4jQuery.create(nodeManager, mappingContext, evaluationContextProvider, queryMethod); + } else { + return new PartTreeNeo4jQuery(nodeManager, mappingContext, queryMethod); } - - return new PartTreeNeo4jQuery(nodeManager, mappingContext, queryMethod); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java index 3d3665a9a..2de60721b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java @@ -33,6 +33,7 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.SpelEvaluator; import org.springframework.data.repository.query.SpelQueryContext; import org.springframework.data.repository.query.SpelQueryContext.SpelExtractor; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -112,6 +113,26 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery { cypherTemplate, queryAnnotation.count(), queryAnnotation.exists(), queryAnnotation.delete()); } + /** + * Create a {@link StringBasedNeo4jQuery} based on an explicit Cypher template. + * + * @param nodeManager + * @param mappingContext + * @param evaluationContextProvider + * @param queryMethod + * @param cypherTemplate The template to use. + * @return A new instance of a String based Neo4j query. + */ + static StringBasedNeo4jQuery create(NodeManager nodeManager, Neo4jMappingContext mappingContext, + QueryMethodEvaluationContextProvider evaluationContextProvider, + Neo4jQueryMethod queryMethod, String cypherTemplate) { + + Assert.hasText(cypherTemplate, "Cannot create String based Neo4j query without a cypher template."); + + return new StringBasedNeo4jQuery(nodeManager, mappingContext, evaluationContextProvider, queryMethod, + cypherTemplate, false, false, false); + } + private StringBasedNeo4jQuery(NodeManager nodeManager, Neo4jMappingContext mappingContext, QueryMethodEvaluationContextProvider evaluationContextProvider, Neo4jQueryMethod queryMethod, String cypherTemplate, boolean countQuery, diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java index 2e7c46c2c..138cd9204 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java @@ -57,6 +57,9 @@ public interface PersonRepository extends Neo4jRepository getOptionalPersonViaQuery(@Param("part1") String part1, @Param("part2") String part2); + Optional getOptionalPersonViaNamedQuery(@Param("part1") String part1, + @Param("part2") String part2); + @Query("MATCH (n:PersonWithNoConstructor) return n") List getAllPersonsWithNoConstructorViaQuery(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java index c0b420a13..32c2f56c6 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java @@ -345,6 +345,13 @@ class RepositoryIT { assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME); } + @Test + void loadOptionalPersonWithAllConstructorWithSpelParametersAndNamedQuery() { + Optional person = repository.getOptionalPersonViaNamedQuery(TEST_PERSON1_NAME.substring(0, 2), TEST_PERSON1_NAME.substring(2)); + assertThat(person).isPresent(); + assertThat(person.get().getName()).isEqualTo(TEST_PERSON1_NAME); + } + @Test void loadAllPersonsWithNoConstructor() { List persons = repository.getAllPersonsWithNoConstructorViaQuery(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java index be8c885ef..e9f8b628b 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/RepositoryQueryTest.java @@ -117,6 +117,22 @@ final class RepositoryQueryTest { PROJECTION_FACTORY, namedQueries); assertThat(query).isInstanceOf(StringBasedNeo4jQuery.class); } + + @Test + void shouldSelectStringBasedNeo4jQueryForNamedQuery() { + + final String namedQueryName = "TestEntity.findAllByANamedQuery"; + when(namedQueries.hasQuery(namedQueryName)).thenReturn(true); + when(namedQueries.getQuery(namedQueryName)).thenReturn("MATCH (n) RETURN n"); + + final Neo4jQueryLookupStrategy lookupStrategy = new Neo4jQueryLookupStrategy(mock(NodeManager.class), mock( + Neo4jMappingContext.class), QueryMethodEvaluationContextProvider.DEFAULT); + + RepositoryQuery query = lookupStrategy + .resolveQuery(queryMethod("findAllByANamedQuery"), TEST_REPOSITORY_METADATA, + PROJECTION_FACTORY, namedQueries); + assertThat(query).isInstanceOf(StringBasedNeo4jQuery.class); + } } @Nested @@ -246,6 +262,8 @@ final class RepositoryQueryTest { @Query List annotatedQueryWithoutTemplate(); + + List findAllByANamedQuery(); } private RepositoryQueryTest() { diff --git a/spring-data-neo4j/src/test/resources/META-INF/neo4j-named-queries.properties b/spring-data-neo4j/src/test/resources/META-INF/neo4j-named-queries.properties new file mode 100644 index 000000000..6ad8ce6dd --- /dev/null +++ b/spring-data-neo4j/src/test/resources/META-INF/neo4j-named-queries.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2019 "Neo4j," +# Neo4j Sweden AB [https://neo4j.com] +# +# This file is part of Neo4j. +# +# 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. +# +PersonWithAllConstructor.getOptionalPersonViaNamedQuery = MATCH (n:PersonWithAllConstructor{name::#{#part1 + #part2}}) return n