Skip to content

Commit f470690

Browse files
michael-simonsmeistermeier
authored andcommitted
GH-2182 - Add support for projections inside the Neo4j templates.
This adds support for projections inside Neo4j templates in a couple of places. All changes are applied into the reactive and the imperative world: - `saveAs` - `saveAllAs` Methods have been added directly to the `Neo4jOperations`. Those take in a class or interface that should be returned. The same logic as with projections during reads is applied: Only properties and relationships that are part of the projection are save / updated. All others are left alone. This works exactly one level deep as of now and might change in the future with enhanced Spring Data Commons support. Those methods have been defaulted so that they don’t break peoples implementation of the operations. In addition, `FluentNeo4jOperations` and some support classes have been introduced that allow to specify the project for finder methods. The default templates implement does now. Both templates bring now their own `ProjectionFactory`. This closes #2182.
1 parent a92ba14 commit f470690

18 files changed

+2171
-653
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core;
17+
18+
import java.util.Collections;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
24+
import org.apiguardian.api.API;
25+
import org.neo4j.cypherdsl.core.Statement;
26+
import org.springframework.lang.Nullable;
27+
28+
/**
29+
* {@link FluentFindOperation} allows creation and execution of Neo4j find operations in a fluent API style.
30+
* <br />
31+
* The starting {@literal domainType} is used for mapping the query provided via {@code by} into the
32+
* Neo4j specific representation. By default, the originating {@literal domainType} is also used for mapping back the
33+
* result. However, it is possible to define an different {@literal returnType} via
34+
* {@code as} to mapping the result.<br />
35+
*
36+
* @author Michael Simons
37+
* @since 6.1
38+
*/
39+
@API(status = API.Status.STABLE, since = "6.1")
40+
public interface FluentFindOperation {
41+
42+
/**
43+
* Start creating a find operation for the given {@literal domainType}.
44+
*
45+
* @param domainType must not be {@literal null}.
46+
* @return new instance of {@link ExecutableFind}.
47+
* @throws IllegalArgumentException if domainType is {@literal null}.
48+
*/
49+
<T> ExecutableFind<T> find(Class<T> domainType);
50+
51+
/**
52+
* Trigger find execution by calling one of the terminating methods from a state where no query is yet defined.
53+
*
54+
* @param <T> returned type
55+
*/
56+
interface TerminatingFindWithoutQuery<T> {
57+
58+
/**
59+
* Get all matching elements.
60+
*
61+
* @return never {@literal null}.
62+
*/
63+
List<T> all();
64+
}
65+
66+
/**
67+
* Triggers find execution by calling one of the terminating methods.
68+
*
69+
* @param <T> returned type
70+
*/
71+
interface TerminatingFind<T> extends TerminatingFindWithoutQuery<T> {
72+
73+
/**
74+
* Get exactly zero or one result.
75+
*
76+
* @return {@link Optional#empty()} if no match found.
77+
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
78+
*/
79+
default Optional<T> one() {
80+
return Optional.ofNullable(oneValue());
81+
}
82+
83+
/**
84+
* Get exactly zero or one result.
85+
*
86+
* @return {@literal null} if no match found.
87+
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
88+
*/
89+
@Nullable
90+
T oneValue();
91+
}
92+
93+
/**
94+
* Terminating operations invoking the actual query execution.
95+
*
96+
* @param <T> returned type
97+
*/
98+
interface FindWithQuery<T> extends TerminatingFindWithoutQuery<T> {
99+
100+
/**
101+
* Set the filter query to be used.
102+
*
103+
* @param query must not be {@literal null}.
104+
* @return new instance of {@link TerminatingFind}.
105+
* @throws IllegalArgumentException if query is {@literal null}.
106+
*/
107+
TerminatingFind<T> matching(String query, Map<String, Object> parameter);
108+
109+
/**
110+
* Set the filter query to be used.
111+
*
112+
* @param query must not be {@literal null}.
113+
* @return new instance of {@link TerminatingFind}.
114+
* @throws IllegalArgumentException if query is {@literal null}.
115+
*/
116+
default TerminatingFind<T> matching(String query) {
117+
return matching(query, Collections.emptyMap());
118+
}
119+
120+
/**
121+
* Set the filter {@link Statement statement} to be used.
122+
*
123+
* @param statement must not be {@literal null}.
124+
* @param parameter Will be merged with parameters in the statement. Parameters in {@code parameter} have precedence.
125+
* @return new instance of {@link TerminatingFind}.
126+
* @throws IllegalArgumentException if statement is {@literal null}.
127+
*/
128+
default TerminatingFind<T> matching(Statement statement, Map<String, Object> parameter) {
129+
Map<String, Object> mergedParameters = new HashMap<>();
130+
mergedParameters.putAll(statement.getParameters());
131+
mergedParameters.putAll(parameter);
132+
return matching(statement.getCypher(), mergedParameters);
133+
}
134+
135+
/**
136+
* Set the filter {@link Statement statement} to be used.
137+
*
138+
* @param statement must not be {@literal null}.
139+
* @return new instance of {@link TerminatingFind}.
140+
* @throws IllegalArgumentException if criteria is {@literal null}.
141+
*/
142+
default TerminatingFind<T> matching(Statement statement) {
143+
return matching(statement, Collections.emptyMap());
144+
}
145+
}
146+
147+
/**
148+
* Result type override (Optional).
149+
*
150+
* @param <T> returned type
151+
*/
152+
interface FindWithProjection<T> extends FindWithQuery<T> {
153+
154+
/**
155+
* Define the target type fields should be mapped to. <br />
156+
* Skip this step if you are anyway only interested in the original domain type.
157+
*
158+
* @param resultType must not be {@literal null}.
159+
* @param <R> result type.
160+
* @return new instance of {@link FindWithProjection}.
161+
* @throws IllegalArgumentException if resultType is {@literal null}.
162+
*/
163+
<R> FindWithQuery<R> as(Class<R> resultType);
164+
}
165+
166+
/**
167+
* Entry point for creating executable find operations.
168+
*
169+
* @param <T> returned type
170+
*/
171+
interface ExecutableFind<T> extends FindWithProjection<T> {
172+
}
173+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Implementation of {@link FluentFindOperation}.
26+
*
27+
* @author Michael J. Simons
28+
* @since 6.1
29+
*/
30+
final class FluentFindOperationSupport implements FluentFindOperation {
31+
32+
private final Neo4jTemplate template;
33+
34+
FluentFindOperationSupport(Neo4jTemplate template) {
35+
this.template = template;
36+
}
37+
38+
@Override
39+
public <T> ExecutableFind<T> find(Class<T> domainType) {
40+
41+
Assert.notNull(domainType, "DomainType must not be null!");
42+
43+
return new ExecutableFindSupport<>(template, domainType, domainType, null, Collections.emptyMap());
44+
}
45+
46+
private static class ExecutableFindSupport<T>
47+
implements ExecutableFind<T>, FindWithProjection<T>, FindWithQuery<T>, TerminatingFind<T> {
48+
49+
private final Neo4jTemplate template;
50+
private final Class<?> domainType;
51+
private final Class<T> returnType;
52+
private final String query;
53+
private final Map<String, Object> parameters;
54+
55+
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
56+
Map<String, Object> parameters) {
57+
this.template = template;
58+
this.domainType = domainType;
59+
this.returnType = returnType;
60+
this.query = query;
61+
this.parameters = parameters;
62+
}
63+
64+
@Override
65+
@SuppressWarnings("HiddenField")
66+
public <T1> FindWithQuery<T1> as(Class<T1> returnType) {
67+
68+
Assert.notNull(returnType, "ReturnType must not be null!");
69+
70+
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
71+
}
72+
73+
@Override
74+
@SuppressWarnings("HiddenField")
75+
public TerminatingFind<T> matching(String query, Map<String, Object> parameters) {
76+
77+
Assert.notNull(query, "Query must not be null!");
78+
79+
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
80+
}
81+
82+
@Override
83+
public T oneValue() {
84+
85+
List<T> result = doFind(TemplateSupport.FetchType.ONE);
86+
if (result.isEmpty()) {
87+
return null;
88+
}
89+
return result.iterator().next();
90+
}
91+
92+
@Override
93+
public List<T> all() {
94+
return doFind(TemplateSupport.FetchType.ALL);
95+
}
96+
97+
private List<T> doFind(TemplateSupport.FetchType fetchType) {
98+
return template.doFind(query, parameters, domainType, returnType, fetchType);
99+
}
100+
}
101+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.core;
17+
18+
import org.apiguardian.api.API;
19+
20+
/**
21+
* An additional interface accompanying the {@link Neo4jOperations} and adding a couple of fluent operations, especially
22+
* around finding and projecting things.
23+
*
24+
* @author Michael J. Simons
25+
* @soundtrack Ozzy Osbourne - Ordinary Man
26+
* @since 6.1
27+
*/
28+
@API(status = API.Status.STABLE, since = "6.1")
29+
public interface FluentNeo4jOperations extends FluentFindOperation {
30+
}

src/main/java/org/springframework/data/neo4j/core/Neo4jOperations.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,19 @@ public interface Neo4jOperations {
180180
*/
181181
<T> T save(T instance);
182182

183+
/**
184+
* Saves an instance of an entity, including the properties and relationship defined by the projected {@code resultType}.
185+
*
186+
* @param instance the entity to be saved. Must not be {@code null}.
187+
* @param <T> the type of the entity.
188+
* @param <R> the type of the projection to be used during save.
189+
* @return the saved, projected instance.
190+
* @since 6.1
191+
*/
192+
default <T, R> R saveAs(T instance, Class<R> resultType) {
193+
throw new UnsupportedOperationException();
194+
}
195+
183196
/**
184197
* Saves several instances of an entity, including all the related entities of the entity.
185198
*
@@ -189,6 +202,19 @@ public interface Neo4jOperations {
189202
*/
190203
<T> List<T> saveAll(Iterable<T> instances);
191204

205+
/**
206+
* Saves an instance of an entity, including the properties and relationship defined by the project {@code resultType}.
207+
*
208+
* @param instances the instances to be saved. Must not be {@code null}.
209+
* @param <T> the type of the entity.
210+
* @param <R> the type of the projection to be used during save.
211+
* @return the saved, projected instance.
212+
* @since 6.1
213+
*/
214+
default <T, R> List<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
215+
throw new UnsupportedOperationException();
216+
}
217+
192218
/**
193219
* Deletes a single entity including all entities related to that entity.
194220
*

0 commit comments

Comments
 (0)