Skip to content

Commit 2bd28be

Browse files
GH-2428 - Provide mapping functions via supplier.
This avoids potentially stale objects during caches in mapping functions (like in our own) and fixes #2428.
1 parent c6daa2f commit 2bd28be

File tree

12 files changed

+73
-48
lines changed

12 files changed

+73
-48
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, String
461461
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
462462
.withCypherQuery(cypherStatement)
463463
.withParameters(parameters)
464-
.usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
464+
.usingMappingFunction(() -> neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
465465
.build();
466466

467467
return toExecutableQuery(preparedQuery);
@@ -677,7 +677,7 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType,
677677

678678
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
679679
.withQueryFragmentsAndParameters(queryFragmentsAndParameters)
680-
.usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
680+
.usingMappingFunction(() -> neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
681681
.build();
682682
return toExecutableQuery(preparedQuery);
683683
}

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.concurrent.atomic.AtomicBoolean;
4141
import java.util.function.BiFunction;
4242
import java.util.function.Function;
43+
import java.util.function.Supplier;
4344
import java.util.stream.Collectors;
4445

4546
/**
@@ -65,29 +66,33 @@ public static <CT> RequiredBuildStep<CT> queryFor(Class<CT> resultType) {
6566

6667
private final Class<T> resultType;
6768
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
68-
private final @Nullable BiFunction<TypeSystem, Record, T> mappingFunction;
69+
private final @Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
70+
private volatile Optional<BiFunction<TypeSystem, Record, T>> lastMappingFunction = Optional.empty();
6971

7072
private PreparedQuery(OptionalBuildSteps<T> optionalBuildSteps) {
7173
this.resultType = optionalBuildSteps.resultType;
72-
if (optionalBuildSteps.mappingFunction == null) {
73-
this.mappingFunction = null;
74-
} else {
75-
this.mappingFunction = (BiFunction<TypeSystem, Record, T>) new AggregatingMappingFunction(
76-
optionalBuildSteps.mappingFunction);
77-
}
74+
this.mappingFunctionSupplier = optionalBuildSteps.mappingFunctionSupplier;
7875
this.queryFragmentsAndParameters = optionalBuildSteps.queryFragmentsAndParameters;
7976
}
8077

8178
public Class<T> getResultType() {
8279
return this.resultType;
8380
}
8481

85-
public Optional<BiFunction<TypeSystem, Record, T>> getOptionalMappingFunction() {
86-
return Optional.ofNullable(mappingFunction);
82+
@SuppressWarnings("unchecked")
83+
public synchronized Optional<BiFunction<TypeSystem, Record, T>> getOptionalMappingFunction() {
84+
lastMappingFunction = Optional.ofNullable(this.mappingFunctionSupplier)
85+
.map(Supplier::get)
86+
.map(f -> (BiFunction<TypeSystem, Record, T>) new AggregatingMappingFunction(f));
87+
return lastMappingFunction;
8788
}
8889

89-
boolean resultsHaveBeenAggregated() {
90-
return this.mappingFunction != null && ((AggregatingMappingFunction) this.mappingFunction).hasAggregated();
90+
synchronized boolean resultsHaveBeenAggregated() {
91+
return lastMappingFunction
92+
.filter(AggregatingMappingFunction.class::isInstance)
93+
.map(AggregatingMappingFunction.class::cast)
94+
.map(AggregatingMappingFunction::hasAggregated)
95+
.orElse(false);
9196
}
9297

9398
public QueryFragmentsAndParameters getQueryFragmentsAndParameters() {
@@ -122,7 +127,7 @@ public static class OptionalBuildSteps<CT> {
122127

123128
final Class<CT> resultType;
124129
final QueryFragmentsAndParameters queryFragmentsAndParameters;
125-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
130+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
126131

127132
OptionalBuildSteps(Class<CT> resultType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
128133
this.resultType = resultType;
@@ -140,9 +145,21 @@ public OptionalBuildSteps<CT> withParameters(Map<String, Object> newParameters)
140145
return this;
141146
}
142147

148+
/**
149+
* @param newMappingFunction A new mapping function
150+
* @return This builder
151+
* @deprecated since 6.1.7, please use {@link #usingMappingFunction(Supplier)}, otherwise your instance will be cached,
152+
* leading to potentially stale results
153+
*/
154+
@Deprecated
143155
public OptionalBuildSteps<CT> usingMappingFunction(
144156
@Nullable BiFunction<TypeSystem, MapAccessor, ?> newMappingFunction) {
145-
this.mappingFunction = newMappingFunction;
157+
this.mappingFunctionSupplier = newMappingFunction == null ? null : () -> newMappingFunction;
158+
return this;
159+
}
160+
161+
public OptionalBuildSteps<CT> usingMappingFunction(@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> newMappingFunction) {
162+
this.mappingFunctionSupplier = newMappingFunction;
146163
return this;
147164
}
148165

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
452452
Assert.notNull(neo4jMappingContext.getPersistentEntity(domainType), "Cannot get or create persistent entity.");
453453
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType).withCypherQuery(cypherQuery)
454454
.withParameters(parameters)
455-
.usingMappingFunction(this.neo4jMappingContext.getRequiredMappingFunctionFor(domainType)).build();
455+
.usingMappingFunction(() -> this.neo4jMappingContext.getRequiredMappingFunctionFor(domainType)).build();
456456
return this.toExecutableQuery(preparedQuery);
457457
}
458458

src/main/java/org/springframework/data/neo4j/repository/query/AbstractNeo4jQuery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ private Slice<?> createSlice(boolean incrementLimit, Neo4jParameterAccessor para
141141
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
142142
List<String> includedProperties, Neo4jParameterAccessor parameterAccessor,
143143
@Nullable Neo4jQueryType queryType,
144-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction,
144+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
145145
@Nullable UnaryOperator<Integer> limitModifier);
146146

147147
protected Optional<PreparedQuery<Long>> getCountQuery(Neo4jParameterAccessor parameterAccessor) {

src/main/java/org/springframework/data/neo4j/repository/query/AbstractReactiveNeo4jQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.List;
1919
import java.util.function.BiFunction;
20+
import java.util.function.Supplier;
2021

2122
import org.neo4j.driver.types.MapAccessor;
2223
import org.neo4j.driver.types.TypeSystem;
@@ -83,5 +84,5 @@ public final Object execute(Object[] parameters) {
8384

8485
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
8586
List<String> includedProperties, Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
86-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction);
87+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction);
8788
}

src/main/java/org/springframework/data/neo4j/repository/query/Neo4jQuerySupport.java

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,28 @@ static Class<?> getDomainType(QueryMethod queryMethod) {
9595
this.queryType = queryType;
9696
}
9797

98-
protected final BiFunction<TypeSystem, MapAccessor, ?> getMappingFunction(final ResultProcessor resultProcessor) {
99-
100-
final ReturnedType returnedTypeMetadata = resultProcessor.getReturnedType();
101-
final Class<?> returnedType = returnedTypeMetadata.getReturnedType();
102-
final Class<?> domainType = returnedTypeMetadata.getDomainType();
103-
104-
final BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
105-
106-
if (mappingContext.getConversionService().isSimpleType(returnedType)) {
107-
// Clients automatically selects a single value mapping function.
108-
// It will throw an error if the query contains more than one column.
109-
mappingFunction = null;
110-
} else if (returnedTypeMetadata.isProjecting()) {
111-
BiFunction<TypeSystem, MapAccessor, ?> target = this.mappingContext.getRequiredMappingFunctionFor(domainType);
112-
mappingFunction = (t, r) -> new EntityInstanceWithSource(target.apply(t, r), t, r);
113-
} else {
114-
mappingFunction = this.mappingContext.getRequiredMappingFunctionFor(domainType);
115-
}
116-
return mappingFunction;
98+
protected final Supplier<BiFunction<TypeSystem, MapAccessor, ?>> getMappingFunction(final ResultProcessor resultProcessor) {
99+
100+
return () -> {
101+
final ReturnedType returnedTypeMetadata = resultProcessor.getReturnedType();
102+
final Class<?> returnedType = returnedTypeMetadata.getReturnedType();
103+
final Class<?> domainType = returnedTypeMetadata.getDomainType();
104+
105+
final BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
106+
107+
if (mappingContext.getConversionService().isSimpleType(returnedType)) {
108+
// Clients automatically selects a single value mapping function.
109+
// It will throw an error if the query contains more than one column.
110+
mappingFunction = null;
111+
} else if (returnedTypeMetadata.isProjecting()) {
112+
BiFunction<TypeSystem, MapAccessor, ?> target = this.mappingContext.getRequiredMappingFunctionFor(
113+
domainType);
114+
mappingFunction = (t, r) -> new EntityInstanceWithSource(target.apply(t, r), t, r);
115+
} else {
116+
mappingFunction = this.mappingContext.getRequiredMappingFunctionFor(domainType);
117+
}
118+
return mappingFunction;
119+
};
117120
}
118121

119122
protected final List<String> getInputProperties(final ResultProcessor resultProcessor) {

src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Optional;
2020
import java.util.function.BiFunction;
21+
import java.util.function.Supplier;
2122
import java.util.function.UnaryOperator;
2223

2324
import org.neo4j.driver.types.MapAccessor;
@@ -60,7 +61,7 @@ private PartTreeNeo4jQuery(Neo4jOperations neo4jOperations, Neo4jMappingContext
6061
@Override
6162
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
6263
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
63-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction, UnaryOperator<Integer> limitModifier) {
64+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction, UnaryOperator<Integer> limitModifier) {
6465

6566
CypherQueryCreator queryCreator = new CypherQueryCreator(mappingContext, getDomainType(queryMethod),
6667
Optional.ofNullable(queryType).orElseGet(() -> Neo4jQueryType.fromPartTree(tree)), tree, parameterAccessor,

src/main/java/org/springframework/data/neo4j/repository/query/ReactivePartTreeNeo4jQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Optional;
2020
import java.util.function.BiFunction;
21+
import java.util.function.Supplier;
2122
import java.util.function.UnaryOperator;
2223

2324
import org.neo4j.driver.types.MapAccessor;
@@ -60,7 +61,7 @@ private ReactivePartTreeNeo4jQuery(ReactiveNeo4jOperations neo4jOperations, Neo4
6061
@Override
6162
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
6263
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
63-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
64+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction) {
6465

6566
CypherQueryCreator queryCreator = new CypherQueryCreator(mappingContext, getDomainType(queryMethod),
6667
Optional.ofNullable(queryType).orElseGet(() -> Neo4jQueryType.fromPartTree(tree)), tree, parameterAccessor,

src/main/java/org/springframework/data/neo4j/repository/query/ReactiveStringBasedNeo4jQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121
import java.util.Optional;
2222
import java.util.function.BiFunction;
23+
import java.util.function.Supplier;
2324

2425
import org.neo4j.driver.types.MapAccessor;
2526
import org.neo4j.driver.types.TypeSystem;
@@ -124,7 +125,7 @@ private ReactiveStringBasedNeo4jQuery(ReactiveNeo4jOperations neo4jOperations, N
124125
@Override
125126
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
126127
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
127-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
128+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction) {
128129

129130
Map<String, Object> boundParameters = bindParameters(parameterAccessor);
130131
QueryContext queryContext = new QueryContext(

src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map.Entry;
2222
import java.util.Optional;
2323
import java.util.function.BiFunction;
24+
import java.util.function.Supplier;
2425
import java.util.function.UnaryOperator;
2526
import java.util.regex.Pattern;
2627

@@ -171,7 +172,7 @@ private StringBasedNeo4jQuery(Neo4jOperations neo4jOperations, Neo4jMappingConte
171172
@Override
172173
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
173174
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
174-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction,
175+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
175176
UnaryOperator<Integer> limitModifier
176177
) {
177178

0 commit comments

Comments
 (0)