Skip to content

Commit e772308

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 559b796 commit e772308

15 files changed

+77
-58
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.function.BiFunction;
3535
import java.util.function.Consumer;
3636
import java.util.function.Function;
37+
import java.util.function.Supplier;
3738
import java.util.stream.Collectors;
3839

3940
import org.apache.commons.logging.LogFactory;
@@ -633,7 +634,7 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nulla
633634
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, @Nullable String cypherStatement,
634635
Map<String, Object> parameters) {
635636

636-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
637+
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction = TemplateSupport
637638
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
638639
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
639640
.withCypherQuery(cypherStatement)
@@ -900,7 +901,7 @@ public <T> ExecutableQuery<T> toExecutableQuery(Class<T> domainType,
900901
private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType,
901902
QueryFragmentsAndParameters queryFragmentsAndParameters) {
902903

903-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
904+
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction = TemplateSupport
904905
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
905906
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType)
906907
.withQueryFragmentsAndParameters(queryFragmentsAndParameters)

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

Lines changed: 19 additions & 15 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,30 +66,34 @@ 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
@SuppressWarnings("unchecked")
7173
private PreparedQuery(OptionalBuildSteps<T> optionalBuildSteps) {
7274
this.resultType = optionalBuildSteps.resultType;
73-
if (optionalBuildSteps.mappingFunction == null) {
74-
this.mappingFunction = null;
75-
} else {
76-
this.mappingFunction = (BiFunction<TypeSystem, Record, T>) new AggregatingMappingFunction(
77-
optionalBuildSteps.mappingFunction);
78-
}
75+
this.mappingFunctionSupplier = optionalBuildSteps.mappingFunctionSupplier;
7976
this.queryFragmentsAndParameters = optionalBuildSteps.queryFragmentsAndParameters;
8077
}
8178

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

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

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

9499
public QueryFragmentsAndParameters getQueryFragmentsAndParameters() {
@@ -123,7 +128,7 @@ public static class OptionalBuildSteps<CT> {
123128

124129
final Class<CT> resultType;
125130
final QueryFragmentsAndParameters queryFragmentsAndParameters;
126-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
131+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunctionSupplier;
127132

128133
OptionalBuildSteps(Class<CT> resultType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
129134
this.resultType = resultType;
@@ -141,9 +146,8 @@ public OptionalBuildSteps<CT> withParameters(Map<String, Object> newParameters)
141146
return this;
142147
}
143148

144-
public OptionalBuildSteps<CT> usingMappingFunction(
145-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> newMappingFunction) {
146-
this.mappingFunction = newMappingFunction;
149+
public OptionalBuildSteps<CT> usingMappingFunction(@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> newMappingFunction) {
150+
this.mappingFunctionSupplier = newMappingFunction;
147151
return this;
148152
}
149153

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import java.util.concurrent.atomic.AtomicReference;
4242
import java.util.function.BiFunction;
4343
import java.util.function.Function;
44+
import java.util.function.Supplier;
4445
import java.util.stream.Collectors;
4546

4647
import org.apache.commons.logging.LogFactory;
@@ -622,7 +623,7 @@ private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType,
622623
private <T> Mono<ExecutableQuery<T>> createExecutableQuery(Class<T> domainType, @Nullable Class<?> resultType, @Nullable String cypherQuery,
623624
Map<String, Object> parameters) {
624625

625-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = TemplateSupport
626+
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction = TemplateSupport
626627
.getAndDecorateMappingFunction(neo4jMappingContext, domainType, resultType);
627628
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType).withCypherQuery(cypherQuery)
628629
.withParameters(parameters)

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.function.BiFunction;
2727
import java.util.function.Function;
2828
import java.util.function.Predicate;
29+
import java.util.function.Supplier;
2930
import java.util.stream.Collectors;
3031
import java.util.stream.StreamSupport;
3132

@@ -230,16 +231,18 @@ Statement toStatement(NodeDescription<?> nodeDescription) {
230231
* @param <T> The domain type
231232
* @return A mapping function
232233
*/
233-
static <T> BiFunction<TypeSystem, MapAccessor, ?> getAndDecorateMappingFunction(
234+
static <T> Supplier<BiFunction<TypeSystem, MapAccessor, ?>> getAndDecorateMappingFunction(
234235
Neo4jMappingContext mappingContext, Class<T> domainType, @Nullable Class<?> resultType) {
235236

236237
Assert.notNull(mappingContext.getPersistentEntity(domainType), "Cannot get or create persistent entity.");
237-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = mappingContext
238-
.getRequiredMappingFunctionFor(domainType);
239-
if (resultType != null && domainType != resultType && !resultType.isInterface()) {
240-
mappingFunction = EntityInstanceWithSource.decorateMappingFunction(mappingFunction);
241-
}
242-
return mappingFunction;
238+
return () -> {
239+
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction = mappingContext.getRequiredMappingFunctionFor(
240+
domainType);
241+
if (resultType != null && domainType != resultType && !resultType.isInterface()) {
242+
mappingFunction = EntityInstanceWithSource.decorateMappingFunction(mappingFunction);
243+
}
244+
return mappingFunction;
245+
};
243246
}
244247

245248
/**

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
@@ -150,7 +150,7 @@ private Slice<?> createSlice(boolean incrementLimit, Neo4jParameterAccessor para
150150
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
151151
Map<PropertyPath, Boolean> includedProperties, Neo4jParameterAccessor parameterAccessor,
152152
@Nullable Neo4jQueryType queryType,
153-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction,
153+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
154154
@Nullable UnaryOperator<Integer> limitModifier);
155155

156156
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.Map;
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;
@@ -91,5 +92,5 @@ public final Object execute(Object[] parameters) {
9192

9293
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
9394
Map<PropertyPath, Boolean> includedProperties, Neo4jParameterAccessor parameterAccessor,
94-
@Nullable Neo4jQueryType queryType, @Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction);
95+
@Nullable Neo4jQueryType queryType, @Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction);
9596
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Map;
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.cypherdsl.core.Statement;
@@ -57,7 +58,7 @@ private CypherdslBasedQuery(Neo4jOperations neo4jOperations,
5758
protected <T> PreparedQuery<T> prepareQuery(Class<T> returnedType,
5859
Map<PropertyPath, Boolean> includedProperties,
5960
Neo4jParameterAccessor parameterAccessor, Neo4jQueryType queryType,
60-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction,
61+
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction,
6162
UnaryOperator<Integer> limitModifier) {
6263

6364
Object[] parameters = parameterAccessor.getValues();

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,27 @@ static Class<?> getDomainType(QueryMethod queryMethod) {
102102
this.queryType = queryType;
103103
}
104104

105-
protected final BiFunction<TypeSystem, MapAccessor, ?> getMappingFunction(final ResultProcessor resultProcessor) {
106-
107-
final ReturnedType returnedTypeMetadata = resultProcessor.getReturnedType();
108-
final Class<?> returnedType = returnedTypeMetadata.getReturnedType();
109-
final Class<?> domainType = returnedTypeMetadata.getDomainType();
110-
111-
final BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
112-
113-
if (mappingContext.getConversionService().isSimpleType(returnedType)) {
114-
// Clients automatically selects a single value mapping function.
115-
// It will throw an error if the query contains more than one column.
116-
mappingFunction = null;
117-
} else if (returnedTypeMetadata.isProjecting()) {
118-
mappingFunction = EntityInstanceWithSource.decorateMappingFunction(this.mappingContext.getRequiredMappingFunctionFor(domainType));
119-
} else {
120-
mappingFunction = this.mappingContext.getRequiredMappingFunctionFor(domainType);
121-
}
122-
return mappingFunction;
105+
protected final Supplier<BiFunction<TypeSystem, MapAccessor, ?>> getMappingFunction(final ResultProcessor resultProcessor) {
106+
107+
return () -> {
108+
final ReturnedType returnedTypeMetadata = resultProcessor.getReturnedType();
109+
final Class<?> returnedType = returnedTypeMetadata.getReturnedType();
110+
final Class<?> domainType = returnedTypeMetadata.getDomainType();
111+
112+
final BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
113+
114+
if (mappingContext.getConversionService().isSimpleType(returnedType)) {
115+
// Clients automatically selects a single value mapping function.
116+
// It will throw an error if the query contains more than one column.
117+
mappingFunction = null;
118+
} else if (returnedTypeMetadata.isProjecting()) {
119+
mappingFunction = EntityInstanceWithSource.decorateMappingFunction(
120+
this.mappingContext.getRequiredMappingFunctionFor(domainType));
121+
} else {
122+
mappingFunction = this.mappingContext.getRequiredMappingFunctionFor(domainType);
123+
}
124+
return mappingFunction;
125+
};
123126
}
124127

125128
private static boolean hasValidReturnTypeForDelete(Neo4jQueryMethod queryMethod) {

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.Map;
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;
@@ -62,7 +63,7 @@ private PartTreeNeo4jQuery(Neo4jOperations neo4jOperations, Neo4jMappingContext
6263
@Override
6364
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, Map<PropertyPath, Boolean> includedProperties,
6465
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
65-
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction, UnaryOperator<Integer> limitModifier) {
66+
@Nullable Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction, UnaryOperator<Integer> limitModifier) {
6667

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

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

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

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

2122
import org.neo4j.cypherdsl.core.Statement;
2223
import org.neo4j.driver.types.MapAccessor;
@@ -52,7 +53,7 @@ private ReactiveCypherdslBasedQuery(ReactiveNeo4jOperations neo4jOperations,
5253
@Override
5354
protected <T> PreparedQuery<T> prepareQuery(Class<T> returnedType, Map<PropertyPath, Boolean> includedProperties,
5455
Neo4jParameterAccessor parameterAccessor, Neo4jQueryType queryType,
55-
BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
56+
Supplier<BiFunction<TypeSystem, MapAccessor, ?>> mappingFunction) {
5657

5758
Object[] parameters = parameterAccessor.getValues();
5859

0 commit comments

Comments
 (0)