DATAGRAPH-1429 - Allow the use of one top-level aggregate.
We have everything in place to call our entity reader several times. The biggest change here is to pass in the map accessor from the outside, not the record, which I should have done when I changed the entity reader to work on the map accessor in the first place.
The prepared query now has a mapping function that knows if it had to unwrap a list before mapping and aggregating it back afterwards. If a list had been unwrapped, then allResults, both the imperative and reactive ones, will react accordingly.
This change allows the flight scenario and a couple of other things to work.
Custom maps can be returned as well, by using DTO based projections with additional properties (returning them as `[{node: n, p1: "p1", p1: "pn“}]` and than applying a custom streamable.
This commit is contained in:
@@ -45,18 +45,18 @@ import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.neo4j.core.mapping.MappingSupport;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipContext;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.MappingSupport;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipContext;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState;
|
||||
import org.springframework.data.neo4j.core.mapping.NodeDescription;
|
||||
import org.springframework.data.neo4j.core.mapping.RelationshipDescription;
|
||||
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
|
||||
import org.springframework.data.neo4j.repository.NoResultException;
|
||||
import org.springframework.data.neo4j.repository.event.BeforeBindCallback;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -118,7 +118,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
public long count(Class<?> domainType) {
|
||||
|
||||
Neo4jPersistentEntity entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
|
||||
Statement statement = cypherGenerator.prepareMatchOf(entityMetaData).returning(Functions.count(asterisk())).build();
|
||||
Statement statement = cypherGenerator.prepareMatchOf(entityMetaData).returning(Functions.count(asterisk()))
|
||||
.build();
|
||||
|
||||
return count(statement);
|
||||
}
|
||||
@@ -189,7 +190,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
public <T> Optional<T> findById(Object id, Class<T> domainType) {
|
||||
Neo4jPersistentEntity<?> entityMetaData = neo4jMappingContext.getPersistentEntity(domainType);
|
||||
Statement statement = cypherGenerator
|
||||
.prepareMatchOf(entityMetaData, entityMetaData.getIdExpression().isEqualTo(parameter(Constants.NAME_OF_ID)))
|
||||
.prepareMatchOf(entityMetaData,
|
||||
entityMetaData.getIdExpression().isEqualTo(parameter(Constants.NAME_OF_ID)))
|
||||
.returning(cypherGenerator.createReturnStatementForMatch(entityMetaData)).build();
|
||||
return createExecutableQuery(domainType, statement, Collections
|
||||
.singletonMap(Constants.NAME_OF_ID, convertIdValues(entityMetaData.getRequiredIdProperty(), id)))
|
||||
@@ -211,7 +213,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
private Object convertIdValues(@Nullable Neo4jPersistentProperty idProperty, Object idValues) {
|
||||
|
||||
return neo4jMappingContext.getConversionService().writeValue(idValues,
|
||||
ClassTypeInformation.from(idValues.getClass()), idProperty == null ? null : idProperty.getOptionalWritingConverter());
|
||||
ClassTypeInformation.from(idValues.getClass()),
|
||||
idProperty == null ? null : idProperty.getOptionalWritingConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -228,7 +231,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
DynamicLabels dynamicLabels = determineDynamicLabels(entityToBeSaved, entityMetaData, inDatabase);
|
||||
|
||||
Optional<Long> optionalInternalId = neo4jClient
|
||||
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels))).in(inDatabase)
|
||||
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels)))
|
||||
.in(inDatabase)
|
||||
.bind((T) entityToBeSaved)
|
||||
.with(neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()))
|
||||
.fetchAs(Long.class).one();
|
||||
@@ -257,7 +261,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
Neo4jClient.RunnableSpecTightToDatabase runnableQuery = neo4jClient
|
||||
.query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData)))
|
||||
.in(inDatabase).bind(propertyAccessor.getProperty(entityMetaData.getRequiredIdProperty()))
|
||||
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()).to(Constants.NAME_OF_STATIC_LABELS_PARAM);
|
||||
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels())
|
||||
.to(Constants.NAME_OF_STATIC_LABELS_PARAM);
|
||||
|
||||
if (entityMetaData.hasVersionProperty()) {
|
||||
runnableQuery = runnableQuery
|
||||
@@ -296,13 +301,16 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
return entities.stream().map(e -> saveImpl(e, databaseName)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<T> entitiesToBeSaved = entities.stream().map(eventSupport::maybeCallBeforeBind).collect(Collectors.toList());
|
||||
List<T> entitiesToBeSaved = entities.stream().map(eventSupport::maybeCallBeforeBind)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Save roots
|
||||
Function<T, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(domainClass);
|
||||
List<Map<String, Object>> entityList = entitiesToBeSaved.stream().map(binderFunction).collect(Collectors.toList());
|
||||
List<Map<String, Object>> entityList = entitiesToBeSaved.stream().map(binderFunction)
|
||||
.collect(Collectors.toList());
|
||||
ResultSummary resultSummary = neo4jClient
|
||||
.query(() -> renderer.render(cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData))).in(databaseName)
|
||||
.query(() -> renderer.render(cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData)))
|
||||
.in(databaseName)
|
||||
.bind(entityList).to(Constants.NAME_OF_ENTITY_LIST_PARAM).run();
|
||||
|
||||
// Save related
|
||||
@@ -385,7 +393,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
|
||||
Assert.notNull(neo4jMappingContext.getPersistentEntity(domainType), "Cannot get or create persistent entity.");
|
||||
PreparedQuery<T> preparedQuery = PreparedQuery.queryFor(domainType).withCypherQuery(cypherStatement)
|
||||
.withParameters(parameters).usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
|
||||
.withParameters(parameters)
|
||||
.usingMappingFunction(neo4jMappingContext.getRequiredMappingFunctionFor(domainType))
|
||||
.build();
|
||||
return toExecutableQuery(preparedQuery);
|
||||
}
|
||||
@@ -466,7 +475,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
if (targetNodeDescription.isUsingInternalIds()) {
|
||||
PersistentPropertyAccessor<?> targetPropertyAccessor = targetNodeDescription
|
||||
.getPropertyAccessor(relatedNode);
|
||||
targetPropertyAccessor.setProperty(targetNodeDescription.getRequiredIdProperty(), relatedInternalId);
|
||||
targetPropertyAccessor
|
||||
.setProperty(targetNodeDescription.getRequiredIdProperty(), relatedInternalId);
|
||||
}
|
||||
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
|
||||
processNestedRelations(targetNodeDescription, relatedNode, inDatabase, stateMachine);
|
||||
@@ -524,8 +534,14 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
this.fetchSpec = fetchSpec;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<T> getResults() {
|
||||
return fetchSpec.all().stream().collect(Collectors.toList());
|
||||
|
||||
Collection<T> all = fetchSpec.all();
|
||||
if (preparedQuery.resultsHaveBeenAggregated()) {
|
||||
return all.stream().flatMap(nested -> ((Collection<T>) nested).stream()).collect(Collectors.toList());
|
||||
}
|
||||
return all.stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Optional<T> getSingleResult() {
|
||||
|
||||
@@ -19,10 +19,12 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -53,7 +55,12 @@ public final class PreparedQuery<T> {
|
||||
|
||||
private PreparedQuery(OptionalBuildSteps<T> optionalBuildSteps) {
|
||||
this.resultType = optionalBuildSteps.resultType;
|
||||
this.mappingFunction = (BiFunction<TypeSystem, Record, T>) optionalBuildSteps.mappingFunction;
|
||||
if (optionalBuildSteps.mappingFunction == null) {
|
||||
this.mappingFunction = null;
|
||||
} else {
|
||||
this.mappingFunction = (BiFunction<TypeSystem, Record, T>) new ListAggregatingMappingFunction(
|
||||
optionalBuildSteps.mappingFunction);
|
||||
}
|
||||
this.cypherQuery = optionalBuildSteps.cypherQuery;
|
||||
this.parameters = optionalBuildSteps.parameters;
|
||||
}
|
||||
@@ -66,6 +73,10 @@ public final class PreparedQuery<T> {
|
||||
return Optional.ofNullable(mappingFunction);
|
||||
}
|
||||
|
||||
boolean resultsHaveBeenAggregated() {
|
||||
return this.mappingFunction != null && ((ListAggregatingMappingFunction) this.mappingFunction).hasAggregated();
|
||||
}
|
||||
|
||||
public String getCypherQuery() {
|
||||
return this.cypherQuery;
|
||||
}
|
||||
@@ -99,7 +110,7 @@ public final class PreparedQuery<T> {
|
||||
final Class<CT> resultType;
|
||||
final String cypherQuery;
|
||||
Map<String, Object> parameters = Collections.emptyMap();
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction;
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
|
||||
|
||||
OptionalBuildSteps(Class<CT> resultType, String cypherQuery) {
|
||||
this.resultType = resultType;
|
||||
@@ -117,7 +128,8 @@ public final class PreparedQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionalBuildSteps<CT> usingMappingFunction(@Nullable BiFunction<TypeSystem, Record, ?> newMappingFunction) {
|
||||
public OptionalBuildSteps<CT> usingMappingFunction(
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> newMappingFunction) {
|
||||
this.mappingFunction = newMappingFunction;
|
||||
return this;
|
||||
}
|
||||
@@ -126,4 +138,27 @@ public final class PreparedQuery<T> {
|
||||
return new PreparedQuery<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListAggregatingMappingFunction implements BiFunction<TypeSystem, Record, Object> {
|
||||
|
||||
private final BiFunction<TypeSystem, MapAccessor, ?> target;
|
||||
private final AtomicBoolean aggregated = new AtomicBoolean(false);
|
||||
|
||||
ListAggregatingMappingFunction(BiFunction<TypeSystem, MapAccessor, ?> target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(TypeSystem t, Record r) {
|
||||
if (r.size() == 1 && r.get(0).hasType(t.LIST())) {
|
||||
aggregated.compareAndSet(false, true);
|
||||
return r.get(0).asList(v -> target.apply(t, v));
|
||||
}
|
||||
return target.apply(t, new RecordMapAccessor(r));
|
||||
}
|
||||
|
||||
boolean hasAggregated() {
|
||||
return aggregated.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,18 +49,18 @@ import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.MappingSupport;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipContext;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.core.mapping.Constants;
|
||||
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipContext;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine;
|
||||
import org.springframework.data.neo4j.core.mapping.NestedRelationshipProcessingStateMachine.ProcessState;
|
||||
import org.springframework.data.neo4j.core.mapping.NodeDescription;
|
||||
import org.springframework.data.neo4j.core.mapping.RelationshipDescription;
|
||||
import org.springframework.data.neo4j.core.mapping.CreateRelationshipStatementHolder;
|
||||
import org.springframework.data.neo4j.repository.event.ReactiveBeforeBindCallback;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -301,16 +301,8 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
|
||||
|
||||
Function<T, Map<String, Object>> binderFunction = neo4jMappingContext.getRequiredBinderFunctionFor(domainClass);
|
||||
return getDatabaseName().flatMapMany(databaseName -> Flux.fromIterable(entities)
|
||||
.flatMap(eventSupport::maybeCallBeforeBind).collectList().flatMapMany(entitiesToBeSaved -> Mono.defer(() -> { // Defer
|
||||
// the
|
||||
// actual
|
||||
// save
|
||||
// statement
|
||||
// until
|
||||
// the
|
||||
// previous
|
||||
// flux
|
||||
// completes
|
||||
.flatMap(eventSupport::maybeCallBeforeBind).collectList().flatMapMany(entitiesToBeSaved -> Mono.defer(() -> {
|
||||
// Defer the actual save statement until the previous flux completes
|
||||
List<Map<String, Object>> boundedEntityList = entitiesToBeSaved.stream().map(binderFunction)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -525,7 +517,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
|
||||
ReactiveNeo4jClient.RecordFetchSpec<T> fetchSpec = preparedQuery.getOptionalMappingFunction()
|
||||
.map(mappingFunction -> mappingSpec.mappedBy(mappingFunction)).orElse(mappingSpec);
|
||||
|
||||
return new DefaultReactiveExecutableQuery<>(fetchSpec);
|
||||
return new DefaultReactiveExecutableQuery<>(preparedQuery, fetchSpec);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -537,17 +529,26 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
|
||||
|
||||
final class DefaultReactiveExecutableQuery<T> implements ExecutableQuery<T> {
|
||||
|
||||
private final PreparedQuery<T> preparedQuery;
|
||||
private final ReactiveNeo4jClient.RecordFetchSpec<T> fetchSpec;
|
||||
|
||||
DefaultReactiveExecutableQuery(ReactiveNeo4jClient.RecordFetchSpec<T> fetchSpec) {
|
||||
DefaultReactiveExecutableQuery(PreparedQuery<T> preparedQuery, ReactiveNeo4jClient.RecordFetchSpec<T> fetchSpec) {
|
||||
this.preparedQuery = preparedQuery;
|
||||
this.fetchSpec = fetchSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All results returned by this query.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Flux<T> getResults() {
|
||||
return fetchSpec.all();
|
||||
|
||||
return fetchSpec.all().switchOnFirst((signal, f) -> {
|
||||
if (preparedQuery.resultsHaveBeenAggregated()) {
|
||||
return f.flatMap(nested -> Flux.fromIterable((Collection<T>) nested));
|
||||
}
|
||||
return f;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.mapping;
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@@ -22,7 +22,7 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.neo4j.core.schema.IdGenerator;
|
||||
@@ -87,12 +87,12 @@ public interface Schema {
|
||||
* @return The default, stateless and reusable mapping function for the given target class
|
||||
* @throws UnknownEntityException When {@code targetClass} is not a managed class
|
||||
*/
|
||||
default <T> BiFunction<TypeSystem, Record, T> getRequiredMappingFunctionFor(Class<T> targetClass) {
|
||||
default <T> BiFunction<TypeSystem, MapAccessor, T> getRequiredMappingFunctionFor(Class<T> targetClass) {
|
||||
NodeDescription<?> nodeDescription = getNodeDescription(targetClass);
|
||||
if (nodeDescription == null) {
|
||||
throw new UnknownEntityException(targetClass);
|
||||
}
|
||||
return (typeSystem, record) -> getEntityConverter().read(targetClass, new RecordMapAccessor(record));
|
||||
return (typeSystem, record) -> getEntityConverter().read(targetClass, record);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -33,7 +33,7 @@ import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.support.PageableExecutionUtils;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -109,7 +109,7 @@ abstract class AbstractNeo4jQuery extends Neo4jQuerySupport implements Repositor
|
||||
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
List<String> includedProperties, Neo4jParameterAccessor parameterAccessor,
|
||||
@Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction);
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction);
|
||||
|
||||
protected Optional<PreparedQuery<Long>> getCountQuery(Neo4jParameterAccessor parameterAccessor) {
|
||||
return Optional.empty();
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.data.neo4j.repository.query;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.neo4j.core.PreparedQuery;
|
||||
@@ -83,5 +83,5 @@ abstract class AbstractReactiveNeo4jQuery extends Neo4jQuerySupport implements R
|
||||
|
||||
protected abstract <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType,
|
||||
List<String> includedProperties, Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction);
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -112,7 +112,7 @@ class DtoInstantiatingConverter implements Converter<EntityInstanceWithSource, O
|
||||
PersistentPropertyAccessor sourceAccessor, EntityInstanceWithSource entityInstanceAndSource) {
|
||||
|
||||
TypeSystem typeSystem = entityInstanceAndSource.getTypeSystem();
|
||||
Record sourceRecord = entityInstanceAndSource.getSourceRecord();
|
||||
MapAccessor sourceRecord = entityInstanceAndSource.getSourceRecord();
|
||||
|
||||
String targetPropertyName = targetProperty.getName();
|
||||
PersistentProperty<?> sourceProperty = sourceEntity.getPersistentProperty(targetPropertyName);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
|
||||
/**
|
||||
@@ -39,9 +39,9 @@ final class EntityInstanceWithSource {
|
||||
/**
|
||||
* The record from which the source above was hydrated and which might contain top level properties that are elligable to mapping.
|
||||
*/
|
||||
private final Record sourceRecord;
|
||||
private final MapAccessor sourceRecord;
|
||||
|
||||
EntityInstanceWithSource(Object entityInstance, TypeSystem typeSystem, Record sourceRecord) {
|
||||
EntityInstanceWithSource(Object entityInstance, TypeSystem typeSystem, MapAccessor sourceRecord) {
|
||||
|
||||
this.entityInstance = entityInstance;
|
||||
this.typeSystem = typeSystem;
|
||||
@@ -56,7 +56,7 @@ final class EntityInstanceWithSource {
|
||||
return typeSystem;
|
||||
}
|
||||
|
||||
public Record getSourceRecord() {
|
||||
public MapAccessor getSourceRecord() {
|
||||
return sourceRecord;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Value;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.data.domain.Range;
|
||||
@@ -91,20 +91,20 @@ abstract class Neo4jQuerySupport {
|
||||
actualParameters);
|
||||
}
|
||||
|
||||
protected final BiFunction<TypeSystem, Record, ?> getMappingFunction(final ResultProcessor resultProcessor) {
|
||||
protected final BiFunction<TypeSystem, MapAccessor, ?> getMappingFunction(final ResultProcessor resultProcessor) {
|
||||
|
||||
final ReturnedType returnedTypeMetadata = resultProcessor.getReturnedType();
|
||||
final Class<?> returnedType = returnedTypeMetadata.getReturnedType();
|
||||
final Class<?> domainType = returnedTypeMetadata.getDomainType();
|
||||
|
||||
final BiFunction<TypeSystem, Record, ?> mappingFunction;
|
||||
final BiFunction<TypeSystem, MapAccessor, ?> mappingFunction;
|
||||
|
||||
if (Neo4jSimpleTypes.HOLDER.isSimpleType(returnedType)) {
|
||||
// Clients automatically selects a single value mapping function.
|
||||
// It will thrown an error if the query contains more than one column.
|
||||
mappingFunction = null;
|
||||
} else if (returnedTypeMetadata.isProjecting()) {
|
||||
BiFunction<TypeSystem, Record, ?> target = this.mappingContext.getRequiredMappingFunctionFor(domainType);
|
||||
BiFunction<TypeSystem, MapAccessor, ?> target = this.mappingContext.getRequiredMappingFunctionFor(domainType);
|
||||
mappingFunction = (t, r) -> new EntityInstanceWithSource(target.apply(t, r), t, r);
|
||||
} else {
|
||||
mappingFunction = this.mappingContext.getRequiredMappingFunctionFor(domainType);
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.neo4j.core.Neo4jOperations;
|
||||
import org.springframework.data.neo4j.core.PreparedQuery;
|
||||
@@ -59,7 +59,7 @@ final class PartTreeNeo4jQuery extends AbstractNeo4jQuery {
|
||||
@Override
|
||||
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
|
||||
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction) {
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
|
||||
|
||||
CypherQueryCreator queryCreator = new CypherQueryCreator(mappingContext, getDomainType(queryMethod),
|
||||
Optional.ofNullable(queryType).orElseGet(() -> Neo4jQueryType.fromPartTree(tree)), tree, parameterAccessor,
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.neo4j.core.PreparedQuery;
|
||||
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
|
||||
@@ -59,7 +59,7 @@ final class ReactivePartTreeNeo4jQuery extends AbstractReactiveNeo4jQuery {
|
||||
@Override
|
||||
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
|
||||
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction) {
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
|
||||
|
||||
CypherQueryCreator queryCreator = new CypherQueryCreator(mappingContext, getDomainType(queryMethod),
|
||||
Optional.ofNullable(queryType).orElseGet(() -> Neo4jQueryType.fromPartTree(tree)), tree, parameterAccessor,
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.neo4j.core.PreparedQuery;
|
||||
@@ -138,7 +138,7 @@ final class ReactiveStringBasedNeo4jQuery extends AbstractReactiveNeo4jQuery {
|
||||
@Override
|
||||
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
|
||||
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction) {
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
|
||||
|
||||
return PreparedQuery.queryFor(returnedType).withCypherQuery(cypherQuery)
|
||||
.withParameters(bindParameters(parameterAccessor)).usingMappingFunction(mappingFunction).build();
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.types.MapAccessor;
|
||||
import org.neo4j.driver.types.TypeSystem;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -135,7 +135,7 @@ final class StringBasedNeo4jQuery extends AbstractNeo4jQuery {
|
||||
@Override
|
||||
protected <T extends Object> PreparedQuery<T> prepareQuery(Class<T> returnedType, List<String> includedProperties,
|
||||
Neo4jParameterAccessor parameterAccessor, @Nullable Neo4jQueryType queryType,
|
||||
@Nullable BiFunction<TypeSystem, Record, ?> mappingFunction) {
|
||||
@Nullable BiFunction<TypeSystem, MapAccessor, ?> mappingFunction) {
|
||||
|
||||
return PreparedQuery.queryFor(returnedType).withCypherQuery(cypherQuery)
|
||||
.withParameters(bindParameters(parameterAccessor)).usingMappingFunction(mappingFunction).build();
|
||||
|
||||
@@ -107,12 +107,10 @@ class Neo4jClientTest {
|
||||
parameters.put("bikeName", "M.*");
|
||||
parameters.put("location", "Sweden");
|
||||
|
||||
String cypher = "MATCH (o:User {name: $name}) - [:OWNS] -> (b:Bike) - [:USED_ON] -> (t:Trip) "
|
||||
+ "WHERE t.takenOn > $aDate " + " AND b.name =~ $bikeName " + " AND t.location = $location " + // TODO Nice
|
||||
// place to
|
||||
// add
|
||||
// coordinates
|
||||
"RETURN b";
|
||||
String cypher = ""
|
||||
+ "MATCH (o:User {name: $name}) - [:OWNS] -> (b:Bike) - [:USED_ON] -> (t:Trip) "
|
||||
+ "WHERE t.takenOn > $aDate " + " AND b.name =~ $bikeName " + " AND t.location = $location "
|
||||
+ "RETURN b";
|
||||
|
||||
Collection<Map<String, Object>> usedBikes = client.query(cypher).bind("michael").to("name").bindAll(parameters)
|
||||
.bind(LocalDate.of(2019, 1, 1)).to("aDate").fetch().all();
|
||||
|
||||
@@ -79,7 +79,6 @@ import org.springframework.data.neo4j.core.convert.Neo4jConversions;
|
||||
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.PersonRepository;
|
||||
import org.springframework.data.neo4j.integration.imperative.repositories.ThingRepository;
|
||||
import org.springframework.data.neo4j.integration.shared.common.KotlinPerson;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltLikedByPersonRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltPerson;
|
||||
@@ -98,9 +97,11 @@ import org.springframework.data.neo4j.integration.shared.common.FriendshipRelati
|
||||
import org.springframework.data.neo4j.integration.shared.common.Hobby;
|
||||
import org.springframework.data.neo4j.integration.shared.common.ImmutablePerson;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Inheritance;
|
||||
import org.springframework.data.neo4j.integration.shared.common.KotlinPerson;
|
||||
import org.springframework.data.neo4j.integration.shared.common.LikesHobbyRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.MultipleLabels;
|
||||
import org.springframework.data.neo4j.integration.shared.common.ParentNode;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Person;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithNoConstructor;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithRelationship;
|
||||
@@ -347,6 +348,51 @@ class RepositoryIT {
|
||||
assertThat(persons).anyMatch(person -> person.getName().equals(TEST_PERSON1_NAME));
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void aggregateThroughQueryIntoListShouldWork(@Autowired PersonRepository repository) {
|
||||
|
||||
List<PersonWithAllConstructor> people = repository.aggregateAllPeople();
|
||||
assertThat(people)
|
||||
.hasSize(2)
|
||||
.extracting(PersonWithAllConstructor::getName)
|
||||
.containsExactlyInAnyOrder(TEST_PERSON1_NAME, TEST_PERSON2_NAME);
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void aggregateThroughQueryIntoCustomObjectShouldWork(@Autowired PersonRepository repository) {
|
||||
|
||||
PersonRepository.CustomAggregation customAggregation = repository.aggregateAllPeopleCustom();
|
||||
assertThat(customAggregation)
|
||||
.hasSize(2)
|
||||
.extracting(PersonWithAllConstructor::getName)
|
||||
.containsExactlyInAnyOrder(TEST_PERSON1_NAME, TEST_PERSON2_NAME);
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void aggregateThroughQueryIntoCustomObjectDTOShouldWork(@Autowired PersonRepository repository) {
|
||||
|
||||
PersonRepository.CustomAggregationOfDto customAggregation = repository
|
||||
.findAllDtoProjectionsWithAdditionalPropertiesAsCustomAggregation(TEST_PERSON1_NAME);
|
||||
assertThat(customAggregation)
|
||||
.isNotEmpty();
|
||||
assertThat(customAggregation.getBySomeLongValue(4711L))
|
||||
.satisfies(dto -> {
|
||||
assertThat(dto.getFirstName()).isEqualTo(TEST_PERSON1_FIRST_NAME);
|
||||
assertThat(dto.getSomeDoubles()).containsExactly(21.42, 42.21);
|
||||
assertThat(dto.getOtherPeople()).hasSize(1)
|
||||
.first()
|
||||
.extracting(PersonWithAllConstructor::getFirstName)
|
||||
.isEqualTo(TEST_PERSON2_FIRST_NAME);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void queryAggregatesShouldWorkWithTheTemplate(@Autowired Neo4jTemplate template) {
|
||||
|
||||
List<Person> people = template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class);
|
||||
assertThat(people).extracting(Person::getFirstName).containsExactly("1", "2", "3", "4", "5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadOnePersonWithAllConstructor(@Autowired PersonRepository repository) {
|
||||
|
||||
|
||||
@@ -17,8 +17,11 @@ package org.springframework.data.neo4j.integration.imperative.repositories;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.neo4j.driver.types.Point;
|
||||
@@ -42,6 +45,7 @@ import org.springframework.data.neo4j.repository.query.BoundingBox;
|
||||
import org.springframework.data.neo4j.repository.query.Query;
|
||||
import org.springframework.data.neo4j.types.GeographicPoint2d;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
@@ -54,6 +58,28 @@ public interface PersonRepository extends Neo4jRepository<PersonWithAllConstruct
|
||||
@Query("RETURN 1")
|
||||
Long customQuery();
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor) return collect(n)")
|
||||
List<PersonWithAllConstructor> aggregateAllPeople();
|
||||
|
||||
/**
|
||||
* A custom aggregate that allows for something like getFriend1, 2 or other stuff...
|
||||
*/
|
||||
class CustomAggregation implements Streamable<PersonWithAllConstructor> {
|
||||
|
||||
private final Streamable<PersonWithAllConstructor> delegate;
|
||||
|
||||
public CustomAggregation(Streamable<PersonWithAllConstructor> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override public Iterator<PersonWithAllConstructor> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor) return collect(n)")
|
||||
CustomAggregation aggregateAllPeopleCustom();
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor) return n")
|
||||
List<PersonWithAllConstructor> getAllPersonsViaQuery();
|
||||
|
||||
@@ -239,6 +265,35 @@ public interface PersonRepository extends Neo4jRepository<PersonWithAllConstruct
|
||||
+ "RETURN n, collect(m) AS otherPeople, 4711 AS someLongValue, [21.42, 42.21] AS someDoubles")
|
||||
List<DtoPersonProjectionContainingAdditionalFields> findAllDtoProjectionsWithAdditionalProperties(@Param("name") String name);
|
||||
|
||||
/**
|
||||
* A custom aggregate that allows for something like getFriend1, 2 or other stuff...
|
||||
*/
|
||||
class CustomAggregationOfDto implements Streamable<DtoPersonProjectionContainingAdditionalFields> {
|
||||
|
||||
private final Streamable<DtoPersonProjectionContainingAdditionalFields> delegate;
|
||||
|
||||
public CustomAggregationOfDto(Streamable<DtoPersonProjectionContainingAdditionalFields> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override public Iterator<DtoPersonProjectionContainingAdditionalFields> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
public DtoPersonProjectionContainingAdditionalFields getBySomeLongValue(long value) {
|
||||
|
||||
return delegate.stream()
|
||||
.collect(Collectors.toMap(DtoPersonProjectionContainingAdditionalFields::getSomeLongValue, Function.identity()))
|
||||
.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(""
|
||||
+ "MATCH (n:PersonWithAllConstructor) where n.name = $name "
|
||||
+ "WITH n MATCH(m:PersonWithAllConstructor) WHERE id(n) <> id(m) "
|
||||
+ "RETURN [{n: n, otherPeople: collect(m), someLongValue: 4711, someDoubles: [21.42, 42.21]}]")
|
||||
CustomAggregationOfDto findAllDtoProjectionsWithAdditionalPropertiesAsCustomAggregation(@Param("name") String name);
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor) where n.name = $name return n{.name}")
|
||||
PersonProjection findByNameWithCustomQueryAndMapProjection(@Param("name") String name);
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
|
||||
import org.springframework.data.neo4j.core.DatabaseSelection;
|
||||
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
|
||||
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
|
||||
import org.springframework.data.neo4j.integration.reactive.repositories.ReactivePersonRepository;
|
||||
import org.springframework.data.neo4j.integration.reactive.repositories.ReactiveThingRepository;
|
||||
import org.springframework.data.neo4j.integration.shared.common.AltHobby;
|
||||
@@ -78,6 +79,7 @@ import org.springframework.data.neo4j.integration.shared.common.Hobby;
|
||||
import org.springframework.data.neo4j.integration.shared.common.ImmutablePerson;
|
||||
import org.springframework.data.neo4j.integration.shared.common.LikesHobbyRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.MultipleLabels;
|
||||
import org.springframework.data.neo4j.integration.shared.common.Person;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithRelationship;
|
||||
import org.springframework.data.neo4j.integration.shared.common.PersonWithRelationshipWithProperties;
|
||||
@@ -144,8 +146,6 @@ class ReactiveRepositoryIT {
|
||||
@Override
|
||||
void setupData(Transaction transaction) {
|
||||
|
||||
transaction.run("MATCH (n) detach delete n");
|
||||
|
||||
id1 = transaction.run("" + "CREATE (n:PersonWithAllConstructor) "
|
||||
+ " SET n.name = $name, n.sameValue = $sameValue, n.first_name = $firstName, n.cool = $cool, n.personNumber = $personNumber, n.bornOn = $bornOn, n.nullable = 'something', n.things = ['a', 'b'], n.place = $place "
|
||||
+ "RETURN id(n)",
|
||||
@@ -503,6 +503,24 @@ class ReactiveRepositoryIT {
|
||||
.expectNextMatches(personList::contains).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void aggregateThroughQueryIntoListShouldWork(@Autowired ReactivePersonRepository repository) {
|
||||
List<PersonWithAllConstructor> personList = Arrays.asList(person1, person2);
|
||||
|
||||
StepVerifier.create(repository.aggregateAllPeople()).expectNextMatches(personList::contains)
|
||||
.expectNextMatches(personList::contains).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAGRAPH-1429
|
||||
void queryAggregatesShouldWorkWithTheTemplate(@Autowired ReactiveNeo4jTemplate template) {
|
||||
|
||||
Flux<Person> people = template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class);
|
||||
|
||||
StepVerifier.create(people.map(Person::getFirstName))
|
||||
.expectNext("1", "2", "3", "4", "5")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadOnePersonWithAllConstructor(@Autowired ReactivePersonRepository repository) {
|
||||
StepVerifier.create(repository.getOnePersonViaQuery()).expectNext(person1).verifyComplete();
|
||||
|
||||
@@ -41,6 +41,9 @@ public interface ReactivePersonRepository extends ReactiveNeo4jRepository<Person
|
||||
@Query("MATCH (n:PersonWithAllConstructor) return n")
|
||||
Flux<PersonWithAllConstructor> getAllPersonsViaQuery();
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor) return collect(n)")
|
||||
Flux<PersonWithAllConstructor> aggregateAllPeople();
|
||||
|
||||
@Query("MATCH (n:PersonWithAllConstructor{name:'Test'}) return n")
|
||||
Mono<PersonWithAllConstructor> getOnePersonViaQuery();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user